CultureQuestORG/SDP2023

View on GitHub
app/src/main/java/ch/epfl/culturequest/authentication/Authenticator.java

Summary

Maintainability
A
1 hr
Test Coverage
B
87%
package ch.epfl.culturequest.authentication;

import static android.app.Activity.RESULT_OK;
import static ch.epfl.culturequest.utils.AndroidUtils.hasConnection;
import static ch.epfl.culturequest.utils.AndroidUtils.showNoConnectionAlert;

import android.content.Intent;

import androidx.activity.ComponentActivity;
import androidx.activity.result.ActivityResultLauncher;

import com.firebase.ui.auth.AuthUI;
import com.firebase.ui.auth.FirebaseAuthUIActivityResultContract;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;

import ch.epfl.culturequest.NavigationActivity;
import ch.epfl.culturequest.ProfileCreatorActivity;
import ch.epfl.culturequest.SignUpActivity;
import ch.epfl.culturequest.database.Database;
import ch.epfl.culturequest.social.Profile;
import ch.epfl.culturequest.notifications.FireMessaging;
import ch.epfl.culturequest.utils.AndroidUtils;

/**
 * A authenticator to sign in the app using google.
 * <p>
 * To launch from an activity simply:
 * Instantiate an attribute with new Authenticator(this) and call sign in and sign out methods
 */
public class Authenticator {

    private static final FirebaseAuth authInstance = FirebaseAuth.getInstance();
    private static ActivityResultLauncher<Intent> signInLauncher;
    private static boolean isEmulatorOn = false;

    /**
     * Sets the emulator on for testing purposes
     */
    public static void setEmulatorOn() {
        if (!isEmulatorOn) {
            authInstance.useEmulator("10.0.2.2", 9099);
            isEmulatorOn = true;
        }
    }

    /**
     * sets the Firebase Ui launcher for the sign in intent linked to the parent activity
     *
     * @param activity
     */
    public static void setSignInLauncher(ComponentActivity activity) {
        signInLauncher = activity.registerForActivityResult(new FirebaseAuthUIActivityResultContract(), result -> {
            if (result.getResultCode() == RESULT_OK && getCurrentUser() != null) {
                signIn(activity);
            } else {
                AndroidUtils.redirectToActivity(activity, SignUpActivity.class);
            }
        });
    }

    public static CompletableFuture<AtomicBoolean> deleteCurrentUser() {
        CompletableFuture<AtomicBoolean> future = new CompletableFuture<>();
        getCurrentUser().delete().addOnCompleteListener(task -> {
            if (task.isSuccessful()) {
                future.complete(new AtomicBoolean(true));
            } else {
                future.complete(new AtomicBoolean(false));
            }
        });

        return future;
    }

    /**
     * Automatically Sign Up and In the user to the app by using a tier party service with the Firebase UI
     *
     * @param activity the activity from which the sign in is launched
     */
    public static CompletableFuture<String> signIn(ComponentActivity activity) {
        CompletableFuture<String> future = new CompletableFuture<>();
        if (getCurrentUser() == null) {
            if (hasConnection(activity)) {
                signInLauncher.launch(signInIntent());
                future.complete("User signed in after being signed out with the Firebase UI");
            } else showNoConnectionAlert(activity, "Please try to login again later");
        } else {
                Database.getProfile(getCurrentUser().getUid()).handle((profile, throwable) -> {
                    if (profile != null && throwable == null) {
                        // the following part is used to check at every login if the user has a
                        // new device token
                        List<String> deviceTokens = profile.getDeviceTokens();
                        FireMessaging.getDeviceToken().whenComplete((token, throwable1) -> {
                            // if the current device token is not already in the list, add it
                            // and update the database
                            if (throwable1 == null && token != null && !deviceTokens.contains(token)) {
                                deviceTokens.add(token);
                                profile.setDeviceTokens(deviceTokens);
                                Database.setDeviceTokens(profile.getUid(), deviceTokens);
                            }
                            Profile.setActiveProfile(profile);
                            AndroidUtils.redirectToActivity(activity, NavigationActivity.class);
                            future.complete("User signed in with an existing profile");
                        });
                    } else {
                        AndroidUtils.redirectToActivity(activity, ProfileCreatorActivity.class);
                        future.complete("User signed in with no existing profile");
                    }
                    return null;
                }).exceptionally(throwable -> {
                    // If an error occurs, sign out the user
                    signOut(activity);
                    future.completeExceptionally(throwable);
                    return null;
                });
        }
        return future;
    }

    /**
     * Signs up (creates) new user to the app manually by using the email and password
     *
     * @param email
     * @param password
     */
    public static CompletableFuture<AtomicBoolean> manualSignUp(String email, String password) {
        CompletableFuture<AtomicBoolean> future = new CompletableFuture<>();
        authInstance.createUserWithEmailAndPassword(email, password)
                .addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                        future.complete(new AtomicBoolean(true));
                    } else {
                        future.complete(new AtomicBoolean(false));
                    }
                });
        return future;
    }

    /**
     * Signs in the user to the app manually by using the email and password
     *
     * @param email
     * @param password
     */
    public static CompletableFuture<AtomicBoolean> manualSignIn(String email, String password) {
        CompletableFuture<AtomicBoolean> future = new CompletableFuture<>();
        authInstance.signInWithEmailAndPassword(email, password)
                .addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                        future.complete(new AtomicBoolean(true));
                    } else {
                        future.complete(new AtomicBoolean(false));
                    }
                });
        return future;
    }

    /**
     * Signs out the user from the app
     * If no user is signed in then signing out does nothing
     * Upon completion, must redirect the user to the sign in page
     *
     * @return a future that completes when the user is signed out
     */
    public static CompletableFuture<AtomicBoolean> signOut(ComponentActivity activity) {
        CompletableFuture<AtomicBoolean> future = new CompletableFuture<>();

        if (getCurrentUser() == null) {
            future.complete(new AtomicBoolean(false));
        } else {
            // first sign out the user
            authInstance.signOut();
            // then sign out of firebase so that the user is not automatically signed in
            AuthUI.getInstance().signOut(activity).addOnCompleteListener(task -> {
                AndroidUtils.redirectToActivity(activity, SignUpActivity.class);
                Profile.setActiveProfile(null);
                future.complete(new AtomicBoolean(true));
            });
        }

        return future;
    }

    /**
     * Creates Sign in Intent for Google Authentication
     * We could add more authentication types but for now we only
     * use google
     *
     * @return the Intent for Google Authentication
     */
    private static Intent signInIntent() {
        //leave as a list of providers in case we want to add some later on
        List<AuthUI.IdpConfig> providers = List.of(new AuthUI.IdpConfig.GoogleBuilder().build());

        return AuthUI.getInstance().createSignInIntentBuilder().setAvailableProviders(providers).build();
    }

    /**
     * Checks if the user is logged in, if not redirects to the sign up activity.
     * Useful to check if the user is logged in when the app is opened from a notification.
     *
     * @param activity the activity where the check is done
     */
    public static void checkIfUserIsLoggedIn(ComponentActivity activity) {
        if (getCurrentUser() == null) {
            AndroidUtils.redirectToActivity(activity, SignUpActivity.class);
        }
    }

    /**
     * @return the current user signed in
     */
    public static FirebaseUser getCurrentUser() {
        return authInstance.getCurrentUser();
    }
}