diff --git a/auth/src/main/java/com/firebase/ui/auth/AuthUI.java b/auth/src/main/java/com/firebase/ui/auth/AuthUI.java
deleted file mode 100644
index d389af20b..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/AuthUI.java
+++ /dev/null
@@ -1,1402 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.firebase.ui.auth;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.facebook.login.LoginManager;
-import com.firebase.ui.auth.data.model.FlowParameters;
-import com.firebase.ui.auth.ui.idp.AuthMethodPickerActivity;
-import com.firebase.ui.auth.util.CredentialUtils;
-import com.firebase.ui.auth.util.ExtraConstants;
-import com.firebase.ui.auth.util.GoogleApiUtils;
-import com.firebase.ui.auth.util.Preconditions;
-import com.firebase.ui.auth.util.data.PhoneNumberUtils;
-import com.firebase.ui.auth.util.data.ProviderAvailability;
-import com.firebase.ui.auth.util.data.ProviderUtils;
-import com.google.android.gms.auth.api.signin.GoogleSignIn;
-import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
-import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
-import com.google.android.gms.common.api.ApiException;
-import com.google.android.gms.common.api.CommonStatusCodes;
-import com.google.android.gms.common.api.Scope;
-import com.google.android.gms.tasks.Task;
-import com.google.android.gms.tasks.Tasks;
-import com.google.firebase.FirebaseApp;
-import com.google.firebase.auth.ActionCodeSettings;
-import com.google.firebase.auth.AuthCredential;
-import com.google.firebase.auth.AuthResult;
-import com.google.firebase.auth.EmailAuthProvider;
-import com.google.firebase.auth.FacebookAuthProvider;
-import com.google.firebase.auth.FirebaseAuth;
-import com.google.firebase.auth.FirebaseAuthInvalidUserException;
-import com.google.firebase.auth.FirebaseAuthProvider;
-import com.google.firebase.auth.FirebaseUser;
-import com.google.firebase.auth.GithubAuthProvider;
-import com.google.firebase.auth.GoogleAuthProvider;
-import com.google.firebase.auth.PhoneAuthProvider;
-import com.google.firebase.auth.TwitterAuthProvider;
-import com.google.firebase.auth.UserInfo;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.DrawableRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.StringDef;
-import androidx.annotation.StyleRes;
-
-/**
- * The entry point to the AuthUI authentication flow, and related utility methods. If your
- * application uses the default {@link FirebaseApp} instance, an AuthUI instance can be retrieved
- * simply by calling {@link AuthUI#getInstance()}. If an alternative app instance is in use, call
- * {@link AuthUI#getInstance(FirebaseApp)} instead, passing the appropriate app instance.
- *
- *
- * See the
- * README
- * for examples on how to get started with FirebaseUI Auth.
- */
-public final class AuthUI {
-
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public static final String TAG = "AuthUI";
-
- /**
- * Provider for anonymous users.
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public static final String ANONYMOUS_PROVIDER = "anonymous";
- public static final String EMAIL_LINK_PROVIDER = EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD;
-
- public static final String MICROSOFT_PROVIDER = "microsoft.com";
- public static final String YAHOO_PROVIDER = "yahoo.com";
- public static final String APPLE_PROVIDER = "apple.com";
-
- /**
- * Default value for logo resource, omits the logo from the {@link AuthMethodPickerActivity}.
- */
- public static final int NO_LOGO = -1;
-
- /**
- * The set of authentication providers supported in Firebase Auth UI.
- */
- public static final Set SUPPORTED_PROVIDERS =
- Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
- GoogleAuthProvider.PROVIDER_ID,
- FacebookAuthProvider.PROVIDER_ID,
- TwitterAuthProvider.PROVIDER_ID,
- GithubAuthProvider.PROVIDER_ID,
- EmailAuthProvider.PROVIDER_ID,
- PhoneAuthProvider.PROVIDER_ID,
- ANONYMOUS_PROVIDER,
- EMAIL_LINK_PROVIDER
- )));
-
- /**
- * The set of OAuth2.0 providers supported in Firebase Auth UI through Generic IDP (web flow).
- */
- public static final Set SUPPORTED_OAUTH_PROVIDERS =
- Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
- MICROSOFT_PROVIDER,
- YAHOO_PROVIDER,
- APPLE_PROVIDER,
- TwitterAuthProvider.PROVIDER_ID,
- GithubAuthProvider.PROVIDER_ID
- )));
-
- /**
- * The set of social authentication providers supported in Firebase Auth UI using their SDK.
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public static final Set SOCIAL_PROVIDERS =
- Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
- GoogleAuthProvider.PROVIDER_ID,
- FacebookAuthProvider.PROVIDER_ID)));
-
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public static final String UNCONFIGURED_CONFIG_VALUE = "CHANGE-ME";
-
- private static final IdentityHashMap INSTANCES = new IdentityHashMap<>();
-
- private static Context sApplicationContext;
-
- private final FirebaseApp mApp;
- private final FirebaseAuth mAuth;
-
- private String mEmulatorHost = null;
- private int mEmulatorPort = -1;
-
- private AuthUI(FirebaseApp app) {
- mApp = app;
- mAuth = FirebaseAuth.getInstance(mApp);
-
- try {
- mAuth.setFirebaseUIVersion(BuildConfig.VERSION_NAME);
- } catch (Exception e) {
- Log.e(TAG, "Couldn't set the FUI version.", e);
- }
- mAuth.useAppLanguage();
- }
-
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @NonNull
- public static Context getApplicationContext() {
- return sApplicationContext;
- }
-
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public static void setApplicationContext(@NonNull Context context) {
- sApplicationContext = Preconditions.checkNotNull(context, "App context cannot be null.")
- .getApplicationContext();
- }
-
- /**
- * Retrieves the {@link AuthUI} instance associated with the default app, as returned by {@code
- * FirebaseApp.getInstance()}.
- *
- * @throws IllegalStateException if the default app is not initialized.
- */
- @NonNull
- public static AuthUI getInstance() {
- return getInstance(FirebaseApp.getInstance());
- }
-
- /**
- * Retrieves the {@link AuthUI} instance associated the the specified app name.
- *
- * @throws IllegalStateException if the app is not initialized.
- */
- @NonNull
- public static AuthUI getInstance(@NonNull String appName) {
- return getInstance(FirebaseApp.getInstance(appName));
- }
-
- /**
- * Retrieves the {@link AuthUI} instance associated the the specified app.
- */
- @NonNull
- public static AuthUI getInstance(@NonNull FirebaseApp app) {
- String releaseUrl = "https://github.com/firebase/FirebaseUI-Android/releases/tag/6.2.0";
- String devWarning = "Beginning with FirebaseUI 6.2.0 you no longer need to include %s to " +
- "sign in with %s. Go to %s for more information";
- if (ProviderAvailability.IS_TWITTER_AVAILABLE) {
- Log.w(TAG, String.format(devWarning, "the TwitterKit SDK", "Twitter", releaseUrl));
- }
- if (ProviderAvailability.IS_GITHUB_AVAILABLE) {
- Log.w(TAG, String.format(devWarning, "com.firebaseui:firebase-ui-auth-github",
- "GitHub", releaseUrl));
- }
-
- AuthUI authUi;
- synchronized (INSTANCES) {
- authUi = INSTANCES.get(app);
- if (authUi == null) {
- authUi = new AuthUI(app);
- INSTANCES.put(app, authUi);
- }
- }
- return authUi;
- }
-
- @NonNull
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public FirebaseApp getApp() {
- return mApp;
- }
-
- @NonNull
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public FirebaseAuth getAuth() {
- return mAuth;
- }
-
- /**
- * Returns true if AuthUI can handle the intent.
- *
- * AuthUI handle the intent when the embedded data is an email link. If it is, you can then
- * specify the link in {@link SignInIntentBuilder#setEmailLink(String)} before starting AuthUI
- * and it will be handled immediately.
- */
- public static boolean canHandleIntent(@NonNull Intent intent) {
- if (intent == null || intent.getData() == null) {
- return false;
- }
- String link = intent.getData().toString();
- return FirebaseAuth.getInstance().isSignInWithEmailLink(link);
- }
-
- /**
- * Default theme used by {@link SignInIntentBuilder#setTheme(int)} if no theme customization is
- * required.
- */
- @StyleRes
- public static int getDefaultTheme() {
- return R.style.FirebaseUI_DefaultMaterialTheme;
- }
-
- /**
- * Signs the current user out, if one is signed in.
- *
- * @param context the context requesting the user be signed out
- * @return A task which, upon completion, signals that the user has been signed out ({@link
- * Task#isSuccessful()}, or that the sign-out attempt failed unexpectedly !{@link
- * Task#isSuccessful()}).
- */
- @NonNull
- public Task signOut(@NonNull Context context) {
- boolean playServicesAvailable = GoogleApiUtils.isPlayServicesAvailable(context);
- if (!playServicesAvailable) {
- Log.w(TAG, "Google Play services not available during signOut");
- }
-
- return signOutIdps(context).continueWith(task -> {
- task.getResult(); // Propagate exceptions if any.
- mAuth.signOut();
- return null;
- });
- }
-
- /**
- * Delete the user from FirebaseAuth.
- *
- * Any associated saved credentials are not explicitly deleted with the new APIs.
- *
- * @param context the calling {@link Context}.
- */
- @NonNull
- public Task delete(@NonNull final Context context) {
- final FirebaseUser currentUser = mAuth.getCurrentUser();
- if (currentUser == null) {
- return Tasks.forException(new FirebaseAuthInvalidUserException(
- String.valueOf(CommonStatusCodes.SIGN_IN_REQUIRED),
- "No currently signed in user."));
- }
-
- return signOutIdps(context).continueWithTask(task -> {
- task.getResult(); // Propagate exception if there was one.
- return currentUser.delete();
- });
- }
-
- /**
- * Connect to the Firebase Authentication emulator.
- * @see FirebaseAuth#useEmulator(String, int)
- */
- public void useEmulator(@NonNull String host, int port) {
- Preconditions.checkArgument(port >= 0, "Port must be >= 0");
- Preconditions.checkArgument(port <= 65535, "Port must be <= 65535");
- mEmulatorHost = host;
- mEmulatorPort = port;
-
- mAuth.useEmulator(host, port);
- }
-
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public boolean isUseEmulator() {
- return mEmulatorHost != null && mEmulatorPort >= 0;
- }
-
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public String getEmulatorHost() {
- return mEmulatorHost;
- }
-
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public int getEmulatorPort() {
- return mEmulatorPort;
- }
-
- private Task signOutIdps(@NonNull Context context) {
- if (ProviderAvailability.IS_FACEBOOK_AVAILABLE) {
- LoginManager.getInstance().logOut();
- }
- if (GoogleApiUtils.isPlayServicesAvailable(context)) {
- return GoogleSignIn.getClient(context, GoogleSignInOptions.DEFAULT_SIGN_IN).signOut();
- } else {
- return Tasks.forResult((Void) null);
- }
- }
-
- /**
- * Starts the process of creating a sign in intent, with the mandatory application context
- * parameter.
- */
- @NonNull
- public SignInIntentBuilder createSignInIntentBuilder() {
- return new SignInIntentBuilder();
- }
-
- @StringDef({
- GoogleAuthProvider.PROVIDER_ID,
- FacebookAuthProvider.PROVIDER_ID,
- TwitterAuthProvider.PROVIDER_ID,
- GithubAuthProvider.PROVIDER_ID,
- EmailAuthProvider.PROVIDER_ID,
- PhoneAuthProvider.PROVIDER_ID,
- ANONYMOUS_PROVIDER,
- EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface SupportedProvider {
- }
-
- /**
- * Configuration for an identity provider.
- */
- public static final class IdpConfig implements Parcelable {
- public static final Creator CREATOR = new Creator() {
- @Override
- public IdpConfig createFromParcel(Parcel in) {
- return new IdpConfig(in);
- }
-
- @Override
- public IdpConfig[] newArray(int size) {
- return new IdpConfig[size];
- }
- };
-
- private final String mProviderId;
- private final Bundle mParams;
-
- private IdpConfig(
- @SupportedProvider @NonNull String providerId,
- @NonNull Bundle params) {
- mProviderId = providerId;
- mParams = new Bundle(params);
- }
-
- private IdpConfig(Parcel in) {
- mProviderId = in.readString();
- mParams = in.readBundle(getClass().getClassLoader());
- }
-
- @NonNull
- @SupportedProvider
- public String getProviderId() {
- return mProviderId;
- }
-
- /**
- * @return provider-specific options
- */
- @NonNull
- public Bundle getParams() {
- return new Bundle(mParams);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int i) {
- parcel.writeString(mProviderId);
- parcel.writeBundle(mParams);
- }
-
- @Override
- public final boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- IdpConfig config = (IdpConfig) o;
-
- return mProviderId.equals(config.mProviderId);
- }
-
- @Override
- public final int hashCode() {
- return mProviderId.hashCode();
- }
-
- @Override
- public String toString() {
- return "IdpConfig{" +
- "mProviderId='" + mProviderId + '\'' +
- ", mParams=" + mParams +
- '}';
- }
-
- /**
- * Base builder for all authentication providers.
- *
- * @see SignInIntentBuilder#setAvailableProviders(List)
- */
- public static class Builder {
- private final Bundle mParams = new Bundle();
- @SupportedProvider
- private String mProviderId;
-
- protected Builder(@SupportedProvider @NonNull String providerId) {
- if (!SUPPORTED_PROVIDERS.contains(providerId)
- && !SUPPORTED_OAUTH_PROVIDERS.contains(providerId)) {
- throw new IllegalArgumentException("Unknown provider: " + providerId);
- }
- mProviderId = providerId;
- }
-
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @NonNull
- protected final Bundle getParams() {
- return mParams;
- }
-
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- protected void setProviderId(@NonNull String providerId) {
- mProviderId = providerId;
- }
-
- @CallSuper
- @NonNull
- public IdpConfig build() {
- return new IdpConfig(mProviderId, mParams);
- }
- }
-
- /**
- * {@link IdpConfig} builder for the email provider.
- */
- public static final class EmailBuilder extends Builder {
- public EmailBuilder() {
- super(EmailAuthProvider.PROVIDER_ID);
- }
-
- /**
- * Enables or disables creating new accounts in the email sign in flows.
- *
- * Account creation is enabled by default.
- */
- @NonNull
- public EmailBuilder setAllowNewAccounts(boolean allow) {
- getParams().putBoolean(ExtraConstants.ALLOW_NEW_EMAILS, allow);
- return this;
- }
-
- /**
- * Configures the requirement for the user to enter first and last name in the email
- * sign up flow.
- *
- * Name is required by default.
- */
- @NonNull
- public EmailBuilder setRequireName(boolean requireName) {
- getParams().putBoolean(ExtraConstants.REQUIRE_NAME, requireName);
- return this;
- }
-
- /**
- * Enables email link sign in instead of password based sign in. Once enabled, you must
- * pass a valid {@link ActionCodeSettings} object using
- * {@link #setActionCodeSettings(ActionCodeSettings)}
- *
- * You must enable Firebase Dynamic Links in the Firebase Console to use email link
- * sign in.
- *
- * @throws IllegalStateException if {@link ActionCodeSettings} is null or not
- * provided with email link enabled.
- */
- @NonNull
- public EmailBuilder enableEmailLinkSignIn() {
- setProviderId(EMAIL_LINK_PROVIDER);
- return this;
- }
-
- /**
- * Sets the {@link ActionCodeSettings} object to be used for email link sign in.
- *
- * {@link ActionCodeSettings#canHandleCodeInApp()} must be set to true, and a valid
- * continueUrl must be passed via {@link ActionCodeSettings.Builder#setUrl(String)}.
- * This URL must be allowlisted in the Firebase Console.
- *
- * @throws IllegalStateException if canHandleCodeInApp is set to false
- * @throws NullPointerException if ActionCodeSettings is null
- */
- @NonNull
- public EmailBuilder setActionCodeSettings(ActionCodeSettings actionCodeSettings) {
- getParams().putParcelable(ExtraConstants.ACTION_CODE_SETTINGS, actionCodeSettings);
- return this;
- }
-
- /**
- * Disables allowing email link sign in to occur across different devices.
- *
- * This cannot be disabled with anonymous upgrade.
- */
- @NonNull
- public EmailBuilder setForceSameDevice() {
- getParams().putBoolean(ExtraConstants.FORCE_SAME_DEVICE, true);
- return this;
- }
-
- /**
- * Sets a default sign in email, if the given email has been registered before, then
- * it will ask the user for password, if the given email it's not registered, then
- * it starts signing up the default email.
- */
- @NonNull
- public EmailBuilder setDefaultEmail(String email) {
- getParams().putString(ExtraConstants.DEFAULT_EMAIL, email);
- return this;
- }
-
- @Override
- public IdpConfig build() {
- if (super.mProviderId.equals(EMAIL_LINK_PROVIDER)) {
- ActionCodeSettings actionCodeSettings =
- getParams().getParcelable(ExtraConstants.ACTION_CODE_SETTINGS);
- Preconditions.checkNotNull(actionCodeSettings, "ActionCodeSettings cannot be " +
- "null when using email link sign in.");
- if (!actionCodeSettings.canHandleCodeInApp()) {
- // Pre-emptively fail if actionCodeSettings are misconfigured. This would
- // have happened when calling sendSignInLinkToEmail
- throw new IllegalStateException(
- "You must set canHandleCodeInApp in your ActionCodeSettings to " +
- "true for Email-Link Sign-in.");
- }
- }
- return super.build();
- }
- }
-
- /**
- * {@link IdpConfig} builder for the phone provider.
- */
- public static final class PhoneBuilder extends Builder {
- public PhoneBuilder() {
- super(PhoneAuthProvider.PROVIDER_ID);
- }
-
- /**
- * @param number the phone number in international format
- * @see #setDefaultNumber(String, String)
- */
- @NonNull
- public PhoneBuilder setDefaultNumber(@NonNull String number) {
- Preconditions.checkUnset(getParams(),
- "Cannot overwrite previously set phone number",
- ExtraConstants.PHONE,
- ExtraConstants.COUNTRY_ISO,
- ExtraConstants.NATIONAL_NUMBER);
- if (!PhoneNumberUtils.isValid(number)) {
- throw new IllegalStateException("Invalid phone number: " + number);
- }
-
- getParams().putString(ExtraConstants.PHONE, number);
-
- return this;
- }
-
- /**
- * Set the default phone number that will be used to populate the phone verification
- * sign-in flow.
- *
- * @param iso the phone number's country code
- * @param number the phone number in local format
- */
- @NonNull
- public PhoneBuilder setDefaultNumber(@NonNull String iso, @NonNull String number) {
- Preconditions.checkUnset(getParams(),
- "Cannot overwrite previously set phone number",
- ExtraConstants.PHONE,
- ExtraConstants.COUNTRY_ISO,
- ExtraConstants.NATIONAL_NUMBER);
- if (!PhoneNumberUtils.isValidIso(iso)) {
- throw new IllegalStateException("Invalid country iso: " + iso);
- }
-
- getParams().putString(ExtraConstants.COUNTRY_ISO, iso);
- getParams().putString(ExtraConstants.NATIONAL_NUMBER, number);
-
- return this;
- }
-
- /**
- * Set the default country code that will be used in the phone verification sign-in
- * flow.
- *
- * @param iso country iso
- */
- @NonNull
- public PhoneBuilder setDefaultCountryIso(@NonNull String iso) {
- Preconditions.checkUnset(getParams(),
- "Cannot overwrite previously set phone number",
- ExtraConstants.PHONE,
- ExtraConstants.COUNTRY_ISO,
- ExtraConstants.NATIONAL_NUMBER);
- if (!PhoneNumberUtils.isValidIso(iso)) {
- throw new IllegalStateException("Invalid country iso: " + iso);
- }
-
- getParams().putString(ExtraConstants.COUNTRY_ISO,
- iso.toUpperCase(Locale.getDefault()));
-
- return this;
- }
-
-
- /**
- * Sets the country codes available in the country code selector for phone
- * authentication. Takes as input a List of both country isos and codes.
- * This is not to be called with
- * {@link #setBlockedCountries(List)}.
- * If both are called, an exception will be thrown.
- *
- * Inputting an e-164 country code (e.g. '+1') will include all countries with
- * +1 as its code.
- * Example input: {'+52', 'us'}
- * For a list of country iso or codes, see Alpha-2 isos here:
- * https://en.wikipedia.org/wiki/ISO_3166-1
- * and e-164 codes here: https://en.wikipedia.org/wiki/List_of_country_calling_codes
- *
- * @param countries a non empty case insensitive list of country codes
- * and/or isos to be allowlisted
- * @throws IllegalArgumentException if an empty allowlist is provided.
- * @throws NullPointerException if a null allowlist is provided.
- */
- public PhoneBuilder setAllowedCountries(
- @NonNull List countries) {
- if (getParams().containsKey(ExtraConstants.BLOCKLISTED_COUNTRIES)) {
- throw new IllegalStateException(
- "You can either allowlist or blocklist country codes for phone " +
- "authentication.");
- }
-
- String message = "Invalid argument: Only non-%s allowlists are valid. " +
- "To specify no allowlist, do not call this method.";
- Preconditions.checkNotNull(countries, String.format(message, "null"));
- Preconditions.checkArgument(!countries.isEmpty(), String.format
- (message, "empty"));
-
- addCountriesToBundle(countries, ExtraConstants.ALLOWLISTED_COUNTRIES);
- return this;
- }
-
- /**
- * Sets the countries to be removed from the country code selector for phone
- * authentication. Takes as input a List of both country isos and codes.
- * This is not to be called with
- * {@link #setAllowedCountries(List)}.
- * If both are called, an exception will be thrown.
- *
- * Inputting an e-164 country code (e.g. '+1') will include all countries with
- * +1 as its code.
- * Example input: {'+52', 'us'}
- * For a list of country iso or codes, see Alpha-2 codes here:
- * https://en.wikipedia.org/wiki/ISO_3166-1
- * and e-164 codes here: https://en.wikipedia.org/wiki/List_of_country_calling_codes
- *
- * @param countries a non empty case insensitive list of country codes
- * and/or isos to be blocklisted
- * @throws IllegalArgumentException if an empty blocklist is provided.
- * @throws NullPointerException if a null blocklist is provided.
- */
- public PhoneBuilder setBlockedCountries(
- @NonNull List countries) {
- if (getParams().containsKey(ExtraConstants.ALLOWLISTED_COUNTRIES)) {
- throw new IllegalStateException(
- "You can either allowlist or blocklist country codes for phone " +
- "authentication.");
- }
-
- String message = "Invalid argument: Only non-%s blocklists are valid. " +
- "To specify no blocklist, do not call this method.";
- Preconditions.checkNotNull(countries, String.format(message, "null"));
- Preconditions.checkArgument(!countries.isEmpty(), String.format
- (message, "empty"));
-
- addCountriesToBundle(countries, ExtraConstants.BLOCKLISTED_COUNTRIES);
- return this;
- }
-
- @Override
- public IdpConfig build() {
- validateInputs();
- return super.build();
- }
-
- private void addCountriesToBundle(List CountryIsos, String CountryIsoType) {
- ArrayList uppercaseCodes = new ArrayList<>();
- for (String code : CountryIsos) {
- uppercaseCodes.add(code.toUpperCase(Locale.getDefault()));
- }
-
- getParams().putStringArrayList(CountryIsoType, uppercaseCodes);
- }
-
- private void validateInputs() {
- List allowedCountries = getParams().getStringArrayList(
- ExtraConstants.ALLOWLISTED_COUNTRIES);
- List blockedCountries = getParams().getStringArrayList(
- ExtraConstants.BLOCKLISTED_COUNTRIES);
-
- if (allowedCountries != null && blockedCountries != null) {
- throw new IllegalStateException(
- "You can either allowlist or blocked country codes for phone " +
- "authentication.");
- } else if (allowedCountries != null) {
- validateInputs(allowedCountries, true);
-
- } else if (blockedCountries != null) {
- validateInputs(blockedCountries, false);
- }
- }
-
- private void validateInputs(List countries, boolean allowed) {
- validateCountryInput(countries);
- validateDefaultCountryInput(countries, allowed);
- }
-
- private void validateCountryInput(List codes) {
- for (String code : codes) {
- if (!PhoneNumberUtils.isValidIso(code) && !PhoneNumberUtils.isValid(code)) {
- throw new IllegalArgumentException("Invalid input: You must provide a " +
- "valid country iso (alpha-2) or code (e-164). e.g. 'us' or '+1'.");
- }
- }
- }
-
- private void validateDefaultCountryInput(List codes, boolean allowed) {
- // A default iso/code can be set via #setDefaultCountryIso() or #setDefaultNumber()
- if (getParams().containsKey(ExtraConstants.COUNTRY_ISO) ||
- getParams().containsKey(ExtraConstants.PHONE)) {
-
- if (!validateDefaultCountryIso(codes, allowed)
- || !validateDefaultPhoneIsos(codes, allowed)) {
- throw new IllegalArgumentException("Invalid default country iso. Make " +
- "sure it is either part of the allowed list or that you "
- + "haven't blocked it.");
- }
- }
-
- }
-
- private boolean validateDefaultCountryIso(List codes, boolean allowed) {
- String defaultIso = getDefaultIso();
- return isValidDefaultIso(codes, defaultIso, allowed);
- }
-
- private boolean validateDefaultPhoneIsos(List codes, boolean allowed) {
- List phoneIsos = getPhoneIsosFromCode();
- for (String iso : phoneIsos) {
- if (isValidDefaultIso(codes, iso, allowed)) {
- return true;
- }
- }
- return phoneIsos.isEmpty();
- }
-
- private boolean isValidDefaultIso(List codes, String iso, boolean allowed) {
- if (iso == null) return true;
- boolean containsIso = containsCountryIso(codes, iso);
- return containsIso && allowed || !containsIso && !allowed;
-
- }
-
- private boolean containsCountryIso(List codes, String iso) {
- iso = iso.toUpperCase(Locale.getDefault());
- for (String code : codes) {
- if (PhoneNumberUtils.isValidIso(code)) {
- if (code.equals(iso)) {
- return true;
- }
- } else {
- List isos = PhoneNumberUtils.getCountryIsosFromCountryCode(code);
- if (isos.contains(iso)) {
- return true;
- }
- }
- }
- return false;
- }
-
- private List getPhoneIsosFromCode() {
- List isos = new ArrayList<>();
- String phone = getParams().getString(ExtraConstants.PHONE);
- if (phone != null && phone.startsWith("+")) {
- String countryCode = "+" + PhoneNumberUtils.getPhoneNumber(phone)
- .getCountryCode();
- List isosToAdd = PhoneNumberUtils.
- getCountryIsosFromCountryCode(countryCode);
- if (isosToAdd != null) {
- isos.addAll(isosToAdd);
- }
- }
- return isos;
- }
-
- private String getDefaultIso() {
- return getParams().containsKey(ExtraConstants.COUNTRY_ISO) ?
- getParams().getString(ExtraConstants.COUNTRY_ISO) : null;
- }
- }
-
- /**
- * {@link IdpConfig} builder for the Google provider.
- */
- public static final class GoogleBuilder extends Builder {
- public GoogleBuilder() {
- super(GoogleAuthProvider.PROVIDER_ID);
- }
-
- private void validateWebClientId() {
- Preconditions.checkConfigured(getApplicationContext(),
- "Check your google-services plugin configuration, the" +
- " default_web_client_id string wasn't populated.",
- R.string.default_web_client_id);
- }
-
- /**
- * Set the scopes that your app will request when using Google sign-in. See all available
- * scopes.
- *
- * @param scopes additional scopes to be requested
- */
- @NonNull
- public GoogleBuilder setScopes(@NonNull List scopes) {
- GoogleSignInOptions.Builder builder =
- new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
- .requestEmail();
- for (String scope : scopes) {
- builder.requestScopes(new Scope(scope));
- }
- return setSignInOptions(builder.build());
- }
-
- /**
- * Set the {@link GoogleSignInOptions} to be used for Google sign-in. Standard
- * options like requesting the user's email will automatically be added.
- *
- * @param options sign-in options
- */
- @NonNull
- public GoogleBuilder setSignInOptions(@NonNull GoogleSignInOptions options) {
- Preconditions.checkUnset(getParams(),
- "Cannot overwrite previously set sign-in options.",
- ExtraConstants.GOOGLE_SIGN_IN_OPTIONS);
-
- GoogleSignInOptions.Builder builder = new GoogleSignInOptions.Builder(options);
-
- String clientId = options.getServerClientId();
- if (clientId == null) {
- validateWebClientId();
- clientId = getApplicationContext().getString(R.string.default_web_client_id);
- }
-
- // Warn the user that they are _probably_ doing the wrong thing if they
- // have not called requestEmail (see issue #1899 and #1621)
- boolean hasEmailScope = false;
- for (Scope s : options.getScopes()) {
- if ("email".equals(s.getScopeUri())) {
- hasEmailScope = true;
- break;
- }
- }
- if (!hasEmailScope) {
- Log.w(TAG, "The GoogleSignInOptions passed to setSignInOptions does not " +
- "request the 'email' scope. In most cases this is a mistake! " +
- "Call requestEmail() on the GoogleSignInOptions object.");
- }
-
- builder.requestIdToken(clientId);
- getParams().putParcelable(
- ExtraConstants.GOOGLE_SIGN_IN_OPTIONS, builder.build());
-
- return this;
- }
-
- @NonNull
- @Override
- public IdpConfig build() {
- if (!getParams().containsKey(ExtraConstants.GOOGLE_SIGN_IN_OPTIONS)) {
- validateWebClientId();
- setScopes(Collections.emptyList());
- }
-
- return super.build();
- }
- }
-
- /**
- * {@link IdpConfig} builder for the Facebook provider.
- */
- public static final class FacebookBuilder extends Builder {
- private static final String TAG = "FacebookBuilder";
-
- public FacebookBuilder() {
- super(FacebookAuthProvider.PROVIDER_ID);
- if (!ProviderAvailability.IS_FACEBOOK_AVAILABLE) {
- throw new RuntimeException(
- "Facebook provider cannot be configured " +
- "without dependency. Did you forget to add " +
- "'com.facebook.android:facebook-login:VERSION' dependency?");
- }
- Preconditions.checkConfigured(getApplicationContext(),
- "Facebook provider unconfigured. Make sure to add a" +
- " `facebook_application_id` string. See the docs for more info:" +
- " https://github" +
- ".com/firebase/FirebaseUI-Android/blob/master/auth/README" +
- ".md#facebook",
- R.string.facebook_application_id);
- if (getApplicationContext().getString(R.string.facebook_login_protocol_scheme)
- .equals("fbYOUR_APP_ID")) {
- Log.w(TAG, "Facebook provider unconfigured for Chrome Custom Tabs.");
- }
- }
-
- /**
- * Specifies the additional permissions that the application will request in the
- * Facebook Login SDK. Available permissions can be found here.
- */
- @NonNull
- public FacebookBuilder setPermissions(@NonNull List permissions) {
- getParams().putStringArrayList(
- ExtraConstants.FACEBOOK_PERMISSIONS, new ArrayList<>(permissions));
- return this;
- }
- }
-
- /**
- * {@link IdpConfig} builder for the Anonymous provider.
- */
- public static final class AnonymousBuilder extends Builder {
- public AnonymousBuilder() {
- super(ANONYMOUS_PROVIDER);
- }
- }
-
- /**
- * {@link IdpConfig} builder for the Twitter provider.
- */
- public static final class TwitterBuilder extends GenericOAuthProviderBuilder {
- private static final String PROVIDER_NAME = "Twitter";
-
- public TwitterBuilder() {
- super(TwitterAuthProvider.PROVIDER_ID, PROVIDER_NAME,
- R.layout.fui_idp_button_twitter);
- }
- }
-
- /**
- * {@link IdpConfig} builder for the GitHub provider.
- */
- public static final class GitHubBuilder extends GenericOAuthProviderBuilder {
- private static final String PROVIDER_NAME = "Github";
-
- public GitHubBuilder() {
- super(GithubAuthProvider.PROVIDER_ID, PROVIDER_NAME,
- R.layout.fui_idp_button_github);
- }
-
- /**
- * Specifies the additional permissions to be requested.
- *
- * Available permissions can be found
- * here.
- *
- * @deprecated Please use {@link #setScopes(List)} instead.
- */
- @Deprecated
- @NonNull
- public GitHubBuilder setPermissions(@NonNull List permissions) {
- setScopes(permissions);
- return this;
- }
- }
-
- /**
- * {@link IdpConfig} builder for the Apple provider.
- */
- public static final class AppleBuilder extends GenericOAuthProviderBuilder {
- private static final String PROVIDER_NAME = "Apple";
-
- public AppleBuilder() {
- super(APPLE_PROVIDER, PROVIDER_NAME, R.layout.fui_idp_button_apple);
- }
- }
-
- /**
- * {@link IdpConfig} builder for the Microsoft provider.
- */
- public static final class MicrosoftBuilder extends GenericOAuthProviderBuilder {
- private static final String PROVIDER_NAME = "Microsoft";
-
- public MicrosoftBuilder() {
- super(MICROSOFT_PROVIDER, PROVIDER_NAME, R.layout.fui_idp_button_microsoft);
- }
- }
-
- /**
- * {@link IdpConfig} builder for the Yahoo provider.
- */
- public static final class YahooBuilder extends GenericOAuthProviderBuilder {
- private static final String PROVIDER_NAME = "Yahoo";
-
- public YahooBuilder() {
- super(YAHOO_PROVIDER, PROVIDER_NAME, R.layout.fui_idp_button_yahoo);
- }
- }
-
- /**
- * {@link IdpConfig} builder for a Generic OAuth provider.
- */
- public static class GenericOAuthProviderBuilder extends Builder {
-
- public GenericOAuthProviderBuilder(@NonNull String providerId,
- @NonNull String providerName,
- int buttonId) {
- super(providerId);
-
- Preconditions.checkNotNull(providerId, "The provider ID cannot be null.");
- Preconditions.checkNotNull(providerName, "The provider name cannot be null.");
-
- getParams().putString(
- ExtraConstants.GENERIC_OAUTH_PROVIDER_ID, providerId);
- getParams().putString(
- ExtraConstants.GENERIC_OAUTH_PROVIDER_NAME, providerName);
- getParams().putInt(
- ExtraConstants.GENERIC_OAUTH_BUTTON_ID, buttonId);
-
- }
-
- @NonNull
- public GenericOAuthProviderBuilder setScopes(@NonNull List scopes) {
- getParams().putStringArrayList(
- ExtraConstants.GENERIC_OAUTH_SCOPES, new ArrayList<>(scopes));
- return this;
- }
-
- @NonNull
- public GenericOAuthProviderBuilder setCustomParameters(
- @NonNull Map customParameters) {
- getParams().putSerializable(
- ExtraConstants.GENERIC_OAUTH_CUSTOM_PARAMETERS,
- new HashMap<>(customParameters));
- return this;
- }
- }
- }
-
- /**
- * Base builder for both {@link SignInIntentBuilder}.
- */
- @SuppressWarnings(value = "unchecked")
- private abstract class AuthIntentBuilder {
- final List mProviders = new ArrayList<>();
- IdpConfig mDefaultProvider = null;
- int mLogo = NO_LOGO;
- int mTheme = getDefaultTheme();
- String mTosUrl;
- String mPrivacyPolicyUrl;
- boolean mAlwaysShowProviderChoice = false;
- boolean mLockOrientation = false;
- boolean mEnableCredentials = true;
- AuthMethodPickerLayout mAuthMethodPickerLayout = null;
- ActionCodeSettings mPasswordSettings = null;
-
- /**
- * Specifies the theme to use for the application flow. If no theme is specified, a
- * default theme will be used.
- */
- @NonNull
- public T setTheme(@StyleRes int theme) {
- mTheme = Preconditions.checkValidStyle(
- mApp.getApplicationContext(),
- theme,
- "theme identifier is unknown or not a style definition");
- return (T) this;
- }
-
- /**
- * Specifies the logo to use for the {@link AuthMethodPickerActivity}. If no logo is
- * specified, none will be used.
- */
- @NonNull
- public T setLogo(@DrawableRes int logo) {
- mLogo = logo;
- return (T) this;
- }
-
- /**
- * Specifies the terms-of-service URL for the application.
- *
- * @deprecated Please use {@link #setTosAndPrivacyPolicyUrls(String, String)} For the Tos
- * link to be displayed a Privacy Policy url must also be provided.
- */
- @NonNull
- @Deprecated
- public T setTosUrl(@Nullable String tosUrl) {
- mTosUrl = tosUrl;
- return (T) this;
- }
-
- /**
- * Specifies the privacy policy URL for the application.
- *
- * @deprecated Please use {@link #setTosAndPrivacyPolicyUrls(String, String)} For the
- * Privacy Policy link to be displayed a Tos url must also be provided.
- */
- @NonNull
- @Deprecated
- public T setPrivacyPolicyUrl(@Nullable String privacyPolicyUrl) {
- mPrivacyPolicyUrl = privacyPolicyUrl;
- return (T) this;
- }
-
- @NonNull
- public T setTosAndPrivacyPolicyUrls(@NonNull String tosUrl,
- @NonNull String privacyPolicyUrl) {
- Preconditions.checkNotNull(tosUrl, "tosUrl cannot be null");
- Preconditions.checkNotNull(privacyPolicyUrl, "privacyPolicyUrl cannot be null");
- mTosUrl = tosUrl;
- mPrivacyPolicyUrl = privacyPolicyUrl;
- return (T) this;
- }
-
- /**
- * Specifies the set of supported authentication providers. At least one provider must
- * be specified. There may only be one instance of each provider. Anonymous provider cannot
- * be the only provider specified.
- *
- *
If no providers are explicitly specified by calling this method, then the email
- * provider is the default supported provider.
- *
- * @param idpConfigs a list of {@link IdpConfig}s, where each {@link IdpConfig} contains the
- * configuration parameters for the IDP.
- * @throws IllegalStateException if anonymous provider is the only specified provider.
- * @see IdpConfig
- */
- @NonNull
- public T setAvailableProviders(@NonNull List idpConfigs) {
- Preconditions.checkNotNull(idpConfigs, "idpConfigs cannot be null");
- if (idpConfigs.size() == 1 &&
- idpConfigs.get(0).getProviderId().equals(ANONYMOUS_PROVIDER)) {
- throw new IllegalStateException("Sign in as guest cannot be the only sign in " +
- "method. In this case, sign the user in anonymously your self; " +
- "no UI is needed.");
- }
-
- mProviders.clear();
-
- for (IdpConfig config : idpConfigs) {
- if (mProviders.contains(config)) {
- throw new IllegalArgumentException("Each provider can only be set once. "
- + config.getProviderId()
- + " was set twice.");
- } else {
- mProviders.add(config);
- }
- }
-
- return (T) this;
- }
-
- /**
- * Specifies the default authentication provider, bypassing the provider selection screen.
- * The provider here must already be included via {@link #setAvailableProviders(List)}, and
- * this method is incompatible with {@link #setAlwaysShowSignInMethodScreen(boolean)}.
- *
- * @param config the default {@link IdpConfig} to use.
- */
- @NonNull
- public T setDefaultProvider(@Nullable IdpConfig config) {
- if (config != null) {
- if (!mProviders.contains(config)) {
- throw new IllegalStateException(
- "Default provider not in available providers list.");
- }
- if (mAlwaysShowProviderChoice) {
- throw new IllegalStateException(
- "Can't set default provider and always show provider choice.");
- }
- }
- mDefaultProvider = config;
- return (T) this;
- }
-
- /**
- * Enables or disables the use of Credential Manager for Passwords credential selector
- *
- *
Is enabled by default.
- *
- * @param enableCredentials enables credential selector before signup
- */
- @NonNull
- public T setCredentialManagerEnabled(boolean enableCredentials) {
- mEnableCredentials = enableCredentials;
- return (T) this;
- }
-
- /**
- * Set a custom layout for the AuthMethodPickerActivity screen.
- * See {@link AuthMethodPickerLayout}.
- *
- * @param authMethodPickerLayout custom layout descriptor object.
- */
- @NonNull
- public T setAuthMethodPickerLayout(@NonNull AuthMethodPickerLayout authMethodPickerLayout) {
- mAuthMethodPickerLayout = authMethodPickerLayout;
- return (T) this;
- }
-
- /**
- * Forces the sign-in method choice screen to always show, even if there is only
- * a single provider configured.
- *
- *
This is false by default.
- *
- * @param alwaysShow if true, force the sign-in choice screen to show.
- */
- @NonNull
- public T setAlwaysShowSignInMethodScreen(boolean alwaysShow) {
- if (alwaysShow && mDefaultProvider != null) {
- throw new IllegalStateException(
- "Can't show provider choice with a default provider.");
- }
- mAlwaysShowProviderChoice = alwaysShow;
- return (T) this;
- }
-
- /**
- * Enable or disables the orientation for small devices to be locked in
- * Portrait orientation
- *
- *
This is false by default.
- *
- * @param lockOrientation if true, force the activities to be in Portrait orientation.
- */
- @NonNull
- public T setLockOrientation(boolean lockOrientation) {
- mLockOrientation = lockOrientation;
- return (T) this;
- }
-
- /**
- * Set custom settings for the RecoverPasswordActivity.
- *
- * @param passwordSettings to allow additional state via a continue URL.
- */
- @NonNull
- public T setResetPasswordSettings(ActionCodeSettings passwordSettings) {
- mPasswordSettings = passwordSettings;
- return (T) this;
- }
-
- @CallSuper
- @NonNull
- public Intent build() {
- if (mProviders.isEmpty()) {
- mProviders.add(new IdpConfig.EmailBuilder().build());
- }
-
- return KickoffActivity.createIntent(mApp.getApplicationContext(), getFlowParams());
- }
-
- protected abstract FlowParameters getFlowParams();
- }
-
- /**
- * Builder for the intent to start the user authentication flow.
- */
- public final class SignInIntentBuilder extends AuthIntentBuilder {
-
- private String mEmailLink;
- private boolean mEnableAnonymousUpgrade;
-
- private SignInIntentBuilder() {
- super();
- }
-
- /**
- * Specifies the email link to be used for sign in. When set, a sign in attempt will be
- * made immediately.
- */
- @NonNull
- public SignInIntentBuilder setEmailLink(@NonNull final String emailLink) {
- mEmailLink = emailLink;
- return this;
- }
-
- /**
- * Enables upgrading anonymous accounts to full accounts during the sign-in flow.
- * This is disabled by default.
- *
- * @throws IllegalStateException when you attempt to enable anonymous user upgrade
- * without forcing the same device flow for email link sign in.
- */
- @NonNull
- public SignInIntentBuilder enableAnonymousUsersAutoUpgrade() {
- mEnableAnonymousUpgrade = true;
- validateEmailBuilderConfig();
- return this;
- }
-
- private void validateEmailBuilderConfig() {
- for (int i = 0; i < mProviders.size(); i++) {
- IdpConfig config = mProviders.get(i);
- if (config.getProviderId().equals(EMAIL_LINK_PROVIDER)) {
- boolean emailLinkForceSameDevice =
- config.getParams().getBoolean(ExtraConstants.FORCE_SAME_DEVICE, true);
- if (!emailLinkForceSameDevice) {
- throw new IllegalStateException("You must force the same device flow " +
- "when using email link sign in with anonymous user upgrade");
- }
- }
- }
- }
-
- @Override
- protected FlowParameters getFlowParams() {
- return new FlowParameters(
- mApp.getName(),
- mProviders,
- mDefaultProvider,
- mTheme,
- mLogo,
- mTosUrl,
- mPrivacyPolicyUrl,
- mEnableCredentials,
- mEnableAnonymousUpgrade,
- mAlwaysShowProviderChoice,
- mLockOrientation,
- mEmailLink,
- mPasswordSettings,
- mAuthMethodPickerLayout);
- }
- }
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/AuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/AuthUI.kt
new file mode 100644
index 000000000..beb1a54ee
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/AuthUI.kt
@@ -0,0 +1,1288 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.firebase.ui.auth
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import android.text.TextUtils
+import android.util.Log
+import androidx.annotation.*
+import com.facebook.login.LoginManager
+import com.firebase.ui.auth.data.model.FlowParameters
+import com.firebase.ui.auth.ui.idp.AuthMethodPickerActivity
+import com.firebase.ui.auth.util.*
+import com.firebase.ui.auth.util.data.PhoneNumberUtils
+import com.firebase.ui.auth.util.data.ProviderAvailability
+import com.google.android.gms.auth.api.signin.GoogleSignIn
+import com.google.android.gms.auth.api.signin.GoogleSignInOptions
+import com.google.android.gms.common.api.CommonStatusCodes
+import com.google.android.gms.common.api.Scope
+import com.google.android.gms.tasks.Task
+import com.google.android.gms.tasks.Tasks
+import com.google.firebase.FirebaseApp
+import com.google.firebase.auth.*
+import java.util.*
+import kotlin.collections.ArrayList
+import kotlin.collections.HashMap
+import kotlin.collections.HashSet
+
+private const val ANONYMOUS_PROVIDER = "anonymous"
+private const val EMAIL_LINK_PROVIDER = EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD
+private const val MICROSOFT_PROVIDER = "microsoft.com"
+private const val YAHOO_PROVIDER = "yahoo.com"
+private const val APPLE_PROVIDER = "apple.com"
+
+/**
+ * The set of authentication providers supported in Firebase Auth UI.
+ */
+private val SUPPORTED_PROVIDERS: Set = Collections.unmodifiableSet(
+ HashSet(
+ listOf(
+ GoogleAuthProvider.PROVIDER_ID,
+ FacebookAuthProvider.PROVIDER_ID,
+ TwitterAuthProvider.PROVIDER_ID,
+ GithubAuthProvider.PROVIDER_ID,
+ EmailAuthProvider.PROVIDER_ID,
+ PhoneAuthProvider.PROVIDER_ID,
+ ANONYMOUS_PROVIDER,
+ EMAIL_LINK_PROVIDER
+ )
+ )
+)
+
+/**
+ * The set of OAuth2.0 providers supported in Firebase Auth UI through Generic IDP (web flow).
+ */
+private val SUPPORTED_OAUTH_PROVIDERS: Set = Collections.unmodifiableSet(
+ HashSet(
+ listOf(
+ MICROSOFT_PROVIDER,
+ YAHOO_PROVIDER,
+ APPLE_PROVIDER,
+ TwitterAuthProvider.PROVIDER_ID,
+ GithubAuthProvider.PROVIDER_ID
+ )
+ )
+)
+
+/**
+ * The set of social authentication providers supported in Firebase Auth UI using their SDK.
+ */
+private val SOCIAL_PROVIDERS: Set = Collections.unmodifiableSet(
+ HashSet(
+ listOf(
+ GoogleAuthProvider.PROVIDER_ID,
+ FacebookAuthProvider.PROVIDER_ID
+ )
+ )
+)
+
+@Retention(AnnotationRetention.SOURCE)
+@StringDef(
+ GoogleAuthProvider.PROVIDER_ID,
+ FacebookAuthProvider.PROVIDER_ID,
+ TwitterAuthProvider.PROVIDER_ID,
+ GithubAuthProvider.PROVIDER_ID,
+ EmailAuthProvider.PROVIDER_ID,
+ PhoneAuthProvider.PROVIDER_ID,
+ ANONYMOUS_PROVIDER,
+ EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD
+)
+annotation class SupportedProvider
+
+/**
+ * Configuration for an identity provider.
+ */
+class IdpConfig private constructor(
+ @SupportedProvider private val mProviderId: String,
+ private val mParams: Bundle
+) : Parcelable {
+ @SupportedProvider
+ fun getProviderId(): String = mProviderId
+
+ /**
+ * @return provider-specific options
+ */
+ fun getParams(): Bundle = Bundle(mParams)
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeString(mProviderId)
+ parcel.writeBundle(mParams)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || javaClass != other.javaClass) return false
+
+ val config = other as IdpConfig
+ return mProviderId == config.mProviderId
+ }
+
+ override fun hashCode(): Int = mProviderId.hashCode()
+
+ override fun toString(): String {
+ return "IdpConfig{" +
+ "mProviderId='$mProviderId'" +
+ ", mParams=$mParams" +
+ '}'
+ }
+
+ companion object {
+ @JvmField
+ val CREATOR: Parcelable.Creator = object : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): IdpConfig {
+ val providerId = parcel.readString()!!
+ val params = parcel.readBundle(IdpConfig::class.java.classLoader)!!
+ return IdpConfig(providerId, params)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+
+ /**
+ * Base builder for all authentication providers.
+ *
+ * @see SignInIntentBuilder.setAvailableProviders
+ */
+ open class Builder protected constructor(@SupportedProvider providerId: String) {
+ protected val mParams = Bundle()
+ @SupportedProvider
+ protected var mProviderId: String = providerId
+
+ init {
+ require(SUPPORTED_PROVIDERS.contains(providerId) ||
+ SUPPORTED_OAUTH_PROVIDERS.contains(providerId)) {
+ "Unknown provider: $providerId"
+ }
+ }
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ protected fun getParams(): Bundle = mParams
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ protected fun setProviderId(providerId: String) {
+ mProviderId = providerId
+ }
+
+ open fun build(): IdpConfig = IdpConfig(mProviderId, mParams)
+ }
+
+ /**
+ * [IdpConfig] builder for the email provider.
+ */
+ class EmailBuilder : Builder(EmailAuthProvider.PROVIDER_ID) {
+ /**
+ * Enables or disables creating new accounts in the email sign in flows.
+ *
+ * Account creation is enabled by default.
+ */
+ fun setAllowNewAccounts(allow: Boolean): EmailBuilder {
+ mParams.putBoolean(ExtraConstants.ALLOW_NEW_EMAILS, allow)
+ return this
+ }
+
+ /**
+ * Configures the requirement for the user to enter first and last name in the email
+ * sign up flow.
+ *
+ * Name is required by default.
+ */
+ fun setRequireName(requireName: Boolean): EmailBuilder {
+ mParams.putBoolean(ExtraConstants.REQUIRE_NAME, requireName)
+ return this
+ }
+
+ /**
+ * Enables email link sign in instead of password based sign in. Once enabled, you must
+ * pass a valid [ActionCodeSettings] object using [setActionCodeSettings]
+ *
+ * You must enable Firebase Dynamic Links in the Firebase Console to use email link
+ * sign in.
+ *
+ * @throws IllegalStateException if [ActionCodeSettings] is null or not
+ * provided with email link enabled.
+ */
+ fun enableEmailLinkSignIn(): EmailBuilder {
+ setProviderId(EMAIL_LINK_PROVIDER)
+ return this
+ }
+
+ /**
+ * Sets the [ActionCodeSettings] object to be used for email link sign in.
+ *
+ * [ActionCodeSettings.canHandleCodeInApp] must be set to true, and a valid
+ * continueUrl must be passed via [ActionCodeSettings.Builder.setUrl].
+ * This URL must be allowlisted in the Firebase Console.
+ *
+ * @throws IllegalStateException if canHandleCodeInApp is set to false
+ * @throws NullPointerException if ActionCodeSettings is null
+ */
+ fun setActionCodeSettings(actionCodeSettings: ActionCodeSettings): EmailBuilder {
+ mParams.putParcelable(ExtraConstants.ACTION_CODE_SETTINGS, actionCodeSettings)
+ return this
+ }
+
+ /**
+ * Disables allowing email link sign in to occur across different devices.
+ *
+ * This cannot be disabled with anonymous upgrade.
+ */
+ fun setForceSameDevice(): EmailBuilder {
+ mParams.putBoolean(ExtraConstants.FORCE_SAME_DEVICE, true)
+ return this
+ }
+
+ /**
+ * Sets a default sign in email, if the given email has been registered before, then
+ * it will ask the user for password, if the given email it's not registered, then
+ * it starts signing up the default email.
+ */
+ fun setDefaultEmail(email: String): EmailBuilder {
+ mParams.putString(ExtraConstants.DEFAULT_EMAIL, email)
+ return this
+ }
+
+ override fun build(): IdpConfig {
+ if (mProviderId == EMAIL_LINK_PROVIDER) {
+ val actionCodeSettings: ActionCodeSettings? =
+ mParams.getParcelable(ExtraConstants.ACTION_CODE_SETTINGS)
+ requireNotNull(actionCodeSettings) {
+ "ActionCodeSettings cannot be null when using email link sign in."
+ }
+ require(actionCodeSettings.canHandleCodeInApp()) {
+ "You must set canHandleCodeInApp in your ActionCodeSettings to true for " +
+ "Email-Link Sign-in."
+ }
+ }
+ return super.build()
+ }
+ }
+
+ /**
+ * [IdpConfig] builder for the phone provider.
+ */
+ class PhoneBuilder : Builder(PhoneAuthProvider.PROVIDER_ID) {
+ /**
+ * @param number the phone number in international format
+ * @see setDefaultNumber
+ */
+ fun setDefaultNumber(number: String): PhoneBuilder {
+ checkUnset()
+ require(PhoneNumberUtils.isValid(number)) { "Invalid phone number: $number" }
+ mParams.putString(ExtraConstants.PHONE, number)
+ return this
+ }
+
+ /**
+ * Set the default phone number that will be used to populate the phone verification
+ * sign-in flow.
+ *
+ * @param iso the phone number's country code
+ * @param number the phone number in local format
+ */
+ fun setDefaultNumber(iso: String, number: String): PhoneBuilder {
+ checkUnset()
+ require(PhoneNumberUtils.isValidIso(iso)) { "Invalid country iso: $iso" }
+ mParams.putString(ExtraConstants.COUNTRY_ISO, iso)
+ mParams.putString(ExtraConstants.NATIONAL_NUMBER, number)
+ return this
+ }
+
+ /**
+ * Set the default country code that will be used in the phone verification sign-in
+ * flow.
+ *
+ * @param iso country iso
+ */
+ fun setDefaultCountryIso(iso: String): PhoneBuilder {
+ checkUnset()
+ require(PhoneNumberUtils.isValidIso(iso)) { "Invalid country iso: $iso" }
+ mParams.putString(
+ ExtraConstants.COUNTRY_ISO,
+ iso.uppercase(Locale.getDefault())
+ )
+ return this
+ }
+
+ /**
+ * Sets the country codes available in the country code selector for phone
+ * authentication. Takes as input a List of both country isos and codes.
+ * This is not to be called with [setBlockedCountries].
+ * If both are called, an exception will be thrown.
+ *
+ * Inputting an e-164 country code (e.g. '+1') will include all countries with
+ * +1 as its code.
+ * Example input: {'+52', 'us'}
+ * For a list of country iso or codes, see Alpha-2 isos here:
+ * https://en.wikipedia.org/wiki/ISO_3166-1
+ * and e-164 codes here: https://en.wikipedia.org/wiki/List_of_country_calling_codes
+ *
+ * @param countries a non empty case insensitive list of country codes
+ * and/or isos to be allowlisted
+ * @throws IllegalArgumentException if an empty allowlist is provided.
+ * @throws NullPointerException if a null allowlist is provided.
+ */
+ fun setAllowedCountries(countries: List): PhoneBuilder {
+ require(!mParams.containsKey(ExtraConstants.BLOCKLISTED_COUNTRIES)) {
+ "You can either allowlist or blocklist country codes for phone authentication."
+ }
+
+ val message = "Invalid argument: Only non-%s allowlists are valid. " +
+ "To specify no allowlist, do not call this method."
+ requireNotNull(countries) { String.format(message, "null") }
+ require(countries.isNotEmpty()) { String.format(message, "empty") }
+
+ addCountriesToBundle(countries, ExtraConstants.ALLOWLISTED_COUNTRIES)
+ return this
+ }
+
+ /**
+ * Sets the countries to be removed from the country code selector for phone
+ * authentication. Takes as input a List of both country isos and codes.
+ * This is not to be called with [setAllowedCountries].
+ * If both are called, an exception will be thrown.
+ *
+ * Inputting an e-164 country code (e.g. '+1') will include all countries with
+ * +1 as its code.
+ * Example input: {'+52', 'us'}
+ * For a list of country iso or codes, see Alpha-2 codes here:
+ * https://en.wikipedia.org/wiki/ISO_3166-1
+ * and e-164 codes here: https://en.wikipedia.org/wiki/List_of_country_calling_codes
+ *
+ * @param countries a non empty case insensitive list of country codes
+ * and/or isos to be blocklisted
+ * @throws IllegalArgumentException if an empty blocklist is provided.
+ * @throws NullPointerException if a null blocklist is provided.
+ */
+ fun setBlockedCountries(countries: List): PhoneBuilder {
+ require(!mParams.containsKey(ExtraConstants.ALLOWLISTED_COUNTRIES)) {
+ "You can either allowlist or blocklist country codes for phone authentication."
+ }
+
+ val message = "Invalid argument: Only non-%s blocklists are valid. " +
+ "To specify no blocklist, do not call this method."
+ requireNotNull(countries) { String.format(message, "null") }
+ require(countries.isNotEmpty()) { String.format(message, "empty") }
+
+ addCountriesToBundle(countries, ExtraConstants.BLOCKLISTED_COUNTRIES)
+ return this
+ }
+
+ override fun build(): IdpConfig {
+ validateInputs()
+ return super.build()
+ }
+
+ private fun checkUnset() {
+ Preconditions.checkUnset(
+ mParams,
+ "Cannot overwrite previously set phone number",
+ ExtraConstants.PHONE,
+ ExtraConstants.COUNTRY_ISO,
+ ExtraConstants.NATIONAL_NUMBER
+ )
+ }
+
+ private fun addCountriesToBundle(countryIsos: List, countryIsoType: String) {
+ val uppercaseCodes = countryIsos.map { it.uppercase(Locale.getDefault()) }
+ mParams.putStringArrayList(countryIsoType, ArrayList(uppercaseCodes))
+ }
+
+ private fun validateInputs() {
+ val allowedCountries = mParams.getStringArrayList(ExtraConstants.ALLOWLISTED_COUNTRIES)
+ val blockedCountries = mParams.getStringArrayList(ExtraConstants.BLOCKLISTED_COUNTRIES)
+
+ when {
+ allowedCountries != null && blockedCountries != null -> {
+ throw IllegalStateException(
+ "You can either allowlist or blocked country codes for phone authentication."
+ )
+ }
+ allowedCountries != null -> validateInputs(allowedCountries, true)
+ blockedCountries != null -> validateInputs(blockedCountries, false)
+ }
+ }
+
+ private fun validateInputs(countries: List, allowed: Boolean) {
+ validateCountryInput(countries)
+ validateDefaultCountryInput(countries, allowed)
+ }
+
+ private fun validateCountryInput(codes: List) {
+ for (code in codes) {
+ if (!PhoneNumberUtils.isValidIso(code) && !PhoneNumberUtils.isValid(code)) {
+ throw IllegalArgumentException(
+ "Invalid input: You must provide a valid country iso (alpha-2) or " +
+ "code (e-164). e.g. 'us' or '+1'."
+ )
+ }
+ }
+ }
+
+ private fun validateDefaultCountryInput(codes: List, allowed: Boolean) {
+ if (mParams.containsKey(ExtraConstants.COUNTRY_ISO) ||
+ mParams.containsKey(ExtraConstants.PHONE)
+ ) {
+ if (!validateDefaultCountryIso(codes, allowed) ||
+ !validateDefaultPhoneIsos(codes, allowed)
+ ) {
+ throw IllegalArgumentException(
+ "Invalid default country iso. Make sure it is either part of the " +
+ "allowed list or that you haven't blocked it."
+ )
+ }
+ }
+ }
+
+ private fun validateDefaultCountryIso(codes: List, allowed: Boolean): Boolean {
+ val defaultIso = getDefaultIso()
+ return isValidDefaultIso(codes, defaultIso, allowed)
+ }
+
+ private fun validateDefaultPhoneIsos(codes: List, allowed: Boolean): Boolean {
+ val phoneIsos = getPhoneIsosFromCode()
+ for (iso in phoneIsos) {
+ if (isValidDefaultIso(codes, iso, allowed)) {
+ return true
+ }
+ }
+ return phoneIsos.isEmpty()
+ }
+
+ private fun isValidDefaultIso(codes: List, iso: String?, allowed: Boolean): Boolean {
+ if (iso == null) return true
+ val containsIso = containsCountryIso(codes, iso)
+ return containsIso && allowed || !containsIso && !allowed
+ }
+
+ private fun containsCountryIso(codes: List, iso: String): Boolean {
+ val upperIso = iso.uppercase(Locale.getDefault())
+ for (code in codes) {
+ if (PhoneNumberUtils.isValidIso(code)) {
+ if (code == upperIso) {
+ return true
+ }
+ } else {
+ val isos = PhoneNumberUtils.getCountryIsosFromCountryCode(code)
+ if (isos?.contains(upperIso) == true) {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ private fun getPhoneIsosFromCode(): List {
+ val isos = mutableListOf()
+ val phone = mParams.getString(ExtraConstants.PHONE)
+ if (phone != null && phone.startsWith("+")) {
+ val countryCode = "+" + PhoneNumberUtils.getPhoneNumber(phone).countryCode
+ val isosToAdd = PhoneNumberUtils.getCountryIsosFromCountryCode(countryCode)
+ if (isosToAdd != null) {
+ isos.addAll(isosToAdd)
+ }
+ }
+ return isos
+ }
+
+ private fun getDefaultIso(): String? =
+ if (mParams.containsKey(ExtraConstants.COUNTRY_ISO)) {
+ mParams.getString(ExtraConstants.COUNTRY_ISO)
+ } else null
+ }
+
+ /**
+ * [IdpConfig] builder for the Google provider.
+ */
+ class GoogleBuilder : Builder(GoogleAuthProvider.PROVIDER_ID) {
+ private fun validateWebClientId() {
+ Preconditions.checkConfigured(
+ AuthUI.getApplicationContext(),
+ "Check your google-services plugin configuration, the default_web_client_id " +
+ "string wasn't populated.",
+ R.string.default_web_client_id
+ )
+ }
+
+ /**
+ * Set the scopes that your app will request when using Google sign-in. See all
+ * [available scopes](https://developers.google.com/identity/protocols/googlescopes).
+ *
+ * @param scopes additional scopes to be requested
+ */
+ fun setScopes(scopes: List): GoogleBuilder {
+ val builder = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
+ .requestEmail()
+ for (scope in scopes) {
+ builder.requestScopes(Scope(scope))
+ }
+ return setSignInOptions(builder.build())
+ }
+
+ /**
+ * Set the [GoogleSignInOptions] to be used for Google sign-in. Standard
+ * options like requesting the user's email will automatically be added.
+ *
+ * @param options sign-in options
+ */
+ fun setSignInOptions(options: GoogleSignInOptions): GoogleBuilder {
+ Preconditions.checkUnset(
+ mParams,
+ "Cannot overwrite previously set sign-in options.",
+ ExtraConstants.GOOGLE_SIGN_IN_OPTIONS
+ )
+
+ val builder = GoogleSignInOptions.Builder(options)
+
+ var clientId = options.serverClientId
+ if (clientId == null) {
+ validateWebClientId()
+ clientId = AuthUI.getApplicationContext().getString(R.string.default_web_client_id)
+ }
+
+ // Warn the user that they are _probably_ doing the wrong thing if they
+ // have not called requestEmail (see issue #1899 and #1621)
+ var hasEmailScope = false
+ for (s in options.scopes) {
+ if ("email" == s.scopeUri) {
+ hasEmailScope = true
+ break
+ }
+ }
+ if (!hasEmailScope) {
+ Log.w(
+ AuthUI.TAG, "The GoogleSignInOptions passed to setSignInOptions does not " +
+ "request the 'email' scope. In most cases this is a mistake! " +
+ "Call requestEmail() on the GoogleSignInOptions object."
+ )
+ }
+
+ builder.requestIdToken(clientId)
+ mParams.putParcelable(ExtraConstants.GOOGLE_SIGN_IN_OPTIONS, builder.build())
+
+ return this
+ }
+
+ override fun build(): IdpConfig {
+ if (!mParams.containsKey(ExtraConstants.GOOGLE_SIGN_IN_OPTIONS)) {
+ validateWebClientId()
+ setScopes(emptyList())
+ }
+ return super.build()
+ }
+ }
+
+ /**
+ * [IdpConfig] builder for the Facebook provider.
+ */
+ class FacebookBuilder : Builder(FacebookAuthProvider.PROVIDER_ID) {
+ companion object {
+ private const val TAG = "FacebookBuilder"
+ }
+
+ init {
+ if (!ProviderAvailability.IS_FACEBOOK_AVAILABLE) {
+ throw RuntimeException(
+ "Facebook provider cannot be configured without dependency. Did you forget " +
+ "to add 'com.facebook.android:facebook-login:VERSION' dependency?"
+ )
+ }
+ Preconditions.checkConfigured(
+ AuthUI.getApplicationContext(),
+ "Facebook provider unconfigured. Make sure to add a `facebook_application_id` " +
+ "string. See the docs for more info: https://github.com/firebase/" +
+ "FirebaseUI-Android/blob/master/auth/README.md#facebook",
+ R.string.facebook_application_id
+ )
+ if (AuthUI.getApplicationContext().getString(R.string.facebook_login_protocol_scheme) == "fbYOUR_APP_ID") {
+ Log.w(TAG, "Facebook provider unconfigured for Chrome Custom Tabs.")
+ }
+ }
+
+ /**
+ * Specifies the additional permissions that the application will request in the
+ * Facebook Login SDK. Available permissions can be found
+ * [here](https://developers.facebook.com/docs/facebook-login/permissions).
+ */
+ fun setPermissions(permissions: List): FacebookBuilder {
+ mParams.putStringArrayList(
+ ExtraConstants.FACEBOOK_PERMISSIONS,
+ ArrayList(permissions)
+ )
+ return this
+ }
+ }
+
+ /**
+ * [IdpConfig] builder for the Anonymous provider.
+ */
+ class AnonymousBuilder : Builder(ANONYMOUS_PROVIDER)
+
+ /**
+ * [IdpConfig] builder for the Twitter provider.
+ */
+ class TwitterBuilder : GenericOAuthProviderBuilder(
+ TwitterAuthProvider.PROVIDER_ID,
+ "Twitter",
+ R.layout.fui_idp_button_twitter
+ )
+
+ /**
+ * [IdpConfig] builder for the GitHub provider.
+ */
+ class GitHubBuilder : GenericOAuthProviderBuilder(
+ GithubAuthProvider.PROVIDER_ID,
+ "Github",
+ R.layout.fui_idp_button_github
+ ) {
+ /**
+ * Specifies the additional permissions to be requested.
+ *
+ * Available permissions can be found
+ * [here](https://developer.github.com/apps/building-oauth-apps/scopes-for-oauth-apps/#available-scopes).
+ *
+ * @deprecated Please use [setScopes] instead.
+ */
+ @Deprecated("Please use setScopes instead", ReplaceWith("setScopes(permissions)"))
+ fun setPermissions(permissions: List): GitHubBuilder {
+ setScopes(permissions)
+ return this
+ }
+ }
+
+ /**
+ * [IdpConfig] builder for the Apple provider.
+ */
+ class AppleBuilder : GenericOAuthProviderBuilder(
+ APPLE_PROVIDER,
+ "Apple",
+ R.layout.fui_idp_button_apple
+ )
+
+ /**
+ * [IdpConfig] builder for the Microsoft provider.
+ */
+ class MicrosoftBuilder : GenericOAuthProviderBuilder(
+ MICROSOFT_PROVIDER,
+ "Microsoft",
+ R.layout.fui_idp_button_microsoft
+ )
+
+ /**
+ * [IdpConfig] builder for the Yahoo provider.
+ */
+ class YahooBuilder : GenericOAuthProviderBuilder(
+ YAHOO_PROVIDER,
+ "Yahoo",
+ R.layout.fui_idp_button_yahoo
+ )
+
+ /**
+ * [IdpConfig] builder for a Generic OAuth provider.
+ */
+ open class GenericOAuthProviderBuilder(
+ providerId: String,
+ providerName: String,
+ buttonId: Int
+ ) : Builder(providerId) {
+
+ init {
+ requireNotNull(providerId) { "The provider ID cannot be null." }
+ requireNotNull(providerName) { "The provider name cannot be null." }
+
+ mParams.putString(ExtraConstants.GENERIC_OAUTH_PROVIDER_ID, providerId)
+ mParams.putString(ExtraConstants.GENERIC_OAUTH_PROVIDER_NAME, providerName)
+ mParams.putInt(ExtraConstants.GENERIC_OAUTH_BUTTON_ID, buttonId)
+ }
+
+ fun setScopes(scopes: List): GenericOAuthProviderBuilder {
+ mParams.putStringArrayList(
+ ExtraConstants.GENERIC_OAUTH_SCOPES,
+ ArrayList(scopes)
+ )
+ return this
+ }
+
+ fun setCustomParameters(
+ customParameters: Map
+ ): GenericOAuthProviderBuilder {
+ mParams.putSerializable(
+ ExtraConstants.GENERIC_OAUTH_CUSTOM_PARAMETERS,
+ HashMap(customParameters)
+ )
+ return this
+ }
+ }
+}
+
+/**
+ * The entry point to the AuthUI authentication flow, and related utility methods. If your
+ * application uses the default [FirebaseApp] instance, an AuthUI instance can be retrieved
+ * simply by calling [AuthUI.getInstance]. If an alternative app instance is in use, call
+ * [AuthUI.getInstance] instead, passing the appropriate app instance.
+ *
+ * See the
+ * [README](https://github.com/firebase/FirebaseUI-Android/blob/master/auth/README.md#table-of-contents)
+ * for examples on how to get started with FirebaseUI Auth.
+ */
+class AuthUI private constructor(private val mApp: FirebaseApp) {
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ companion object {
+ const val TAG = "AuthUI"
+
+ /**
+ * Provider for anonymous users.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ const val ANONYMOUS_PROVIDER = com.firebase.ui.auth.ANONYMOUS_PROVIDER
+ const val EMAIL_LINK_PROVIDER = com.firebase.ui.auth.EMAIL_LINK_PROVIDER
+ const val MICROSOFT_PROVIDER = com.firebase.ui.auth.MICROSOFT_PROVIDER
+ const val YAHOO_PROVIDER = com.firebase.ui.auth.YAHOO_PROVIDER
+ const val APPLE_PROVIDER = com.firebase.ui.auth.APPLE_PROVIDER
+
+ /**
+ * Default value for logo resource, omits the logo from the [AuthMethodPickerActivity].
+ */
+ const val NO_LOGO = -1
+
+ /**
+ * The set of authentication providers supported in Firebase Auth UI.
+ */
+ @JvmField
+ val SUPPORTED_PROVIDERS: Set = Collections.unmodifiableSet(
+ HashSet(
+ listOf(
+ GoogleAuthProvider.PROVIDER_ID,
+ FacebookAuthProvider.PROVIDER_ID,
+ TwitterAuthProvider.PROVIDER_ID,
+ GithubAuthProvider.PROVIDER_ID,
+ EmailAuthProvider.PROVIDER_ID,
+ PhoneAuthProvider.PROVIDER_ID,
+ ANONYMOUS_PROVIDER,
+ EMAIL_LINK_PROVIDER
+ )
+ )
+ )
+
+ /**
+ * The set of OAuth2.0 providers supported in Firebase Auth UI through Generic IDP (web flow).
+ */
+ @JvmField
+ val SUPPORTED_OAUTH_PROVIDERS: Set = Collections.unmodifiableSet(
+ HashSet(
+ listOf(
+ MICROSOFT_PROVIDER,
+ YAHOO_PROVIDER,
+ APPLE_PROVIDER,
+ TwitterAuthProvider.PROVIDER_ID,
+ GithubAuthProvider.PROVIDER_ID
+ )
+ )
+ )
+
+ /**
+ * The set of social authentication providers supported in Firebase Auth UI using their SDK.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @JvmField
+ val SOCIAL_PROVIDERS: Set = Collections.unmodifiableSet(
+ HashSet(
+ listOf(
+ GoogleAuthProvider.PROVIDER_ID,
+ FacebookAuthProvider.PROVIDER_ID
+ )
+ )
+ )
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ const val UNCONFIGURED_CONFIG_VALUE = "CHANGE-ME"
+
+ private val INSTANCES = IdentityHashMap()
+
+ private var sApplicationContext: Context? = null
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @JvmStatic
+ fun getApplicationContext(): Context {
+ return sApplicationContext!!
+ }
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @JvmStatic
+ fun setApplicationContext(context: Context) {
+ sApplicationContext = Preconditions.checkNotNull(context, "App context cannot be null.")
+ .applicationContext
+ }
+
+ /**
+ * Retrieves the [AuthUI] instance associated with the default app, as returned by
+ * [FirebaseApp.getInstance].
+ *
+ * @throws IllegalStateException if the default app is not initialized.
+ */
+ @JvmStatic
+ fun getInstance(): AuthUI {
+ return getInstance(FirebaseApp.getInstance())
+ }
+
+ /**
+ * Retrieves the [AuthUI] instance associated the the specified app name.
+ *
+ * @throws IllegalStateException if the app is not initialized.
+ */
+ @JvmStatic
+ fun getInstance(appName: String): AuthUI {
+ return getInstance(FirebaseApp.getInstance(appName))
+ }
+
+ /**
+ * Retrieves the [AuthUI] instance associated the the specified app.
+ */
+ @JvmStatic
+ fun getInstance(app: FirebaseApp): AuthUI {
+ val releaseUrl = "https://github.com/firebase/FirebaseUI-Android/releases/tag/6.2.0"
+ val devWarning = "Beginning with FirebaseUI 6.2.0 you no longer need to include %s to " +
+ "sign in with %s. Go to %s for more information"
+ if (ProviderAvailability.IS_TWITTER_AVAILABLE) {
+ Log.w(TAG, String.format(devWarning, "the TwitterKit SDK", "Twitter", releaseUrl))
+ }
+ if (ProviderAvailability.IS_GITHUB_AVAILABLE) {
+ Log.w(
+ TAG, String.format(
+ devWarning, "com.firebaseui:firebase-ui-auth-github",
+ "GitHub", releaseUrl
+ )
+ )
+ }
+
+ synchronized(INSTANCES) {
+ var authUi = INSTANCES[app]
+ if (authUi == null) {
+ authUi = AuthUI(app)
+ INSTANCES[app] = authUi
+ }
+ return authUi
+ }
+ }
+
+ /**
+ * Returns true if AuthUI can handle the intent.
+ *
+ * AuthUI handle the intent when the embedded data is an email link. If it is, you can then
+ * specify the link in [SignInIntentBuilder.setEmailLink] before starting AuthUI
+ * and it will be handled immediately.
+ */
+ @JvmStatic
+ fun canHandleIntent(intent: Intent?): Boolean {
+ if (intent?.data == null) {
+ return false
+ }
+ val link = intent.data.toString()
+ return FirebaseAuth.getInstance().isSignInWithEmailLink(link)
+ }
+
+ /**
+ * Default theme used by [SignInIntentBuilder.setTheme] if no theme customization is
+ * required.
+ */
+ @JvmStatic
+ @StyleRes
+ fun getDefaultTheme(): Int {
+ return R.style.FirebaseUI_DefaultMaterialTheme
+ }
+ }
+
+ private val mAuth: FirebaseAuth = FirebaseAuth.getInstance(mApp)
+ private var mEmulatorHost: String? = null
+ private var mEmulatorPort: Int = -1
+
+ init {
+ try {
+ mAuth.setFirebaseUIVersion(BuildConfig.VERSION_NAME)
+ } catch (e: Exception) {
+ Log.e(TAG, "Couldn't set the FUI version.", e)
+ }
+ mAuth.useAppLanguage()
+ }
+
+ /**
+ * Signs the current user out, if one is signed in.
+ *
+ * @param context the context requesting the user be signed out
+ * @return A task which, upon completion, signals that the user has been signed out
+ * ([Task.isSuccessful], or that the sign-out attempt failed unexpectedly
+ * ![Task.isSuccessful]).
+ */
+ fun signOut(context: Context): Task {
+ val playServicesAvailable = GoogleApiUtils.isPlayServicesAvailable(context)
+ if (!playServicesAvailable) {
+ Log.w(TAG, "Google Play services not available during signOut")
+ }
+
+ return signOutIdps(context).continueWith { task ->
+ task.result // Propagate exceptions if any.
+ mAuth.signOut()
+ null
+ }
+ }
+
+ /**
+ * Delete the user from FirebaseAuth.
+ *
+ * Any associated saved credentials are not explicitly deleted with the new APIs.
+ *
+ * @param context the calling [Context].
+ */
+ fun delete(context: Context): Task {
+ val currentUser = mAuth.currentUser
+ ?: return Tasks.forException(
+ FirebaseAuthInvalidUserException(
+ CommonStatusCodes.SIGN_IN_REQUIRED.toString(),
+ "No currently signed in user."
+ )
+ )
+
+ return signOutIdps(context).continueWithTask { task ->
+ task.result // Propagate exception if there was one.
+ currentUser.delete()
+ }
+ }
+
+ /**
+ * Connect to the Firebase Authentication emulator.
+ * @see FirebaseAuth.useEmulator
+ */
+ fun useEmulator(host: String, port: Int) {
+ require(port in 0..65535) { "Port must be between 0 and 65535" }
+ mEmulatorHost = host
+ mEmulatorPort = port
+ mAuth.useEmulator(host, port)
+ }
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ fun isUseEmulator(): Boolean = mEmulatorHost != null && mEmulatorPort >= 0
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ fun getEmulatorHost(): String? = mEmulatorHost
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ fun getEmulatorPort(): Int = mEmulatorPort
+
+ private fun signOutIdps(context: Context): Task {
+ if (ProviderAvailability.IS_FACEBOOK_AVAILABLE) {
+ LoginManager.getInstance().logOut()
+ }
+ return if (GoogleApiUtils.isPlayServicesAvailable(context)) {
+ GoogleSignIn.getClient(context, GoogleSignInOptions.DEFAULT_SIGN_IN).signOut()
+ } else {
+ Tasks.forResult(null)
+ }
+ }
+
+ /**
+ * Starts the process of creating a sign in intent, with the mandatory application context
+ * parameter.
+ */
+ fun createSignInIntentBuilder(): SignInIntentBuilder {
+ return SignInIntentBuilder()
+ }
+
+ /**
+ * Base builder for both [SignInIntentBuilder].
+ */
+ abstract class AuthIntentBuilder> {
+ private val app: FirebaseApp = mApp
+ protected val mProviders = ArrayList()
+ protected var mDefaultProvider: IdpConfig? = null
+ protected var mLogo = NO_LOGO
+ protected var mTheme = getDefaultTheme()
+ protected var mTosUrl: String? = null
+ protected var mPrivacyPolicyUrl: String? = null
+ protected var mAlwaysShowProviderChoice = false
+ protected var mLockOrientation = false
+ protected var mEnableCredentials = true
+ protected var mAuthMethodPickerLayout: AuthMethodPickerLayout? = null
+ protected var mPasswordSettings: ActionCodeSettings? = null
+
+ /**
+ * Specifies the theme to use for the application flow. If no theme is specified, a
+ * default theme will be used.
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun setTheme(@StyleRes theme: Int): T {
+ mTheme = Preconditions.checkValidStyle(
+ app.applicationContext,
+ theme,
+ "theme identifier is unknown or not a style definition"
+ )
+ return this as T
+ }
+
+ /**
+ * Specifies the logo to use for the [AuthMethodPickerActivity]. If no logo is
+ * specified, none will be used.
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun setLogo(@DrawableRes logo: Int): T {
+ mLogo = logo
+ return this as T
+ }
+
+ /**
+ * Specifies the terms-of-service URL for the application.
+ *
+ * @deprecated Please use [setTosAndPrivacyPolicyUrls] For the Tos
+ * link to be displayed a Privacy Policy url must also be provided.
+ */
+ @Deprecated(
+ "Please use setTosAndPrivacyPolicyUrls. For the Tos link to be displayed a Privacy " +
+ "Policy url must also be provided.",
+ ReplaceWith("setTosAndPrivacyPolicyUrls")
+ )
+ @Suppress("UNCHECKED_CAST")
+ fun setTosUrl(tosUrl: String?): T {
+ mTosUrl = tosUrl
+ return this as T
+ }
+
+ /**
+ * Specifies the privacy policy URL for the application.
+ *
+ * @deprecated Please use [setTosAndPrivacyPolicyUrls] For the
+ * Privacy Policy link to be displayed a Tos url must also be provided.
+ */
+ @Deprecated(
+ "Please use setTosAndPrivacyPolicyUrls. For the Privacy Policy link to be " +
+ "displayed a Tos url must also be provided.",
+ ReplaceWith("setTosAndPrivacyPolicyUrls")
+ )
+ @Suppress("UNCHECKED_CAST")
+ fun setPrivacyPolicyUrl(privacyPolicyUrl: String?): T {
+ mPrivacyPolicyUrl = privacyPolicyUrl
+ return this as T
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ fun setTosAndPrivacyPolicyUrls(tosUrl: String, privacyPolicyUrl: String): T {
+ requireNotNull(tosUrl) { "tosUrl cannot be null" }
+ requireNotNull(privacyPolicyUrl) { "privacyPolicyUrl cannot be null" }
+ mTosUrl = tosUrl
+ mPrivacyPolicyUrl = privacyPolicyUrl
+ return this as T
+ }
+
+ /**
+ * Specifies the set of supported authentication providers. At least one provider must
+ * be specified. There may only be one instance of each provider. Anonymous provider cannot
+ * be the only provider specified.
+ *
+ * If no providers are explicitly specified by calling this method, then the email
+ * provider is the default supported provider.
+ *
+ * @param idpConfigs a list of [IdpConfig]s, where each [IdpConfig] contains the
+ * configuration parameters for the IDP.
+ * @throws IllegalStateException if anonymous provider is the only specified provider.
+ * @see IdpConfig
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun setAvailableProviders(idpConfigs: List): T {
+ requireNotNull(idpConfigs) { "idpConfigs cannot be null" }
+ require(!(idpConfigs.size == 1 && idpConfigs[0].getProviderId() == ANONYMOUS_PROVIDER)) {
+ "Sign in as guest cannot be the only sign in method. In this case, sign the user " +
+ "in anonymously yourself; no UI is needed."
+ }
+
+ mProviders.clear()
+
+ for (config in idpConfigs) {
+ require(!mProviders.contains(config)) {
+ "Each provider can only be set once. ${config.getProviderId()} was set twice."
+ }
+ mProviders.add(config)
+ }
+
+ return this as T
+ }
+
+ /**
+ * Specifies the default authentication provider, bypassing the provider selection screen.
+ * The provider here must already be included via [setAvailableProviders], and
+ * this method is incompatible with [setAlwaysShowSignInMethodScreen].
+ *
+ * @param config the default [IdpConfig] to use.
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun setDefaultProvider(config: IdpConfig?): T {
+ if (config != null) {
+ require(mProviders.contains(config)) {
+ "Default provider not in available providers list."
+ }
+ require(!mAlwaysShowProviderChoice) {
+ "Can't set default provider and always show provider choice."
+ }
+ }
+ mDefaultProvider = config
+ return this as T
+ }
+
+ /**
+ * Enables or disables the use of Credential Manager for Passwords credential selector
+ *
+ * Is enabled by default.
+ *
+ * @param enableCredentials enables credential selector before signup
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun setCredentialManagerEnabled(enableCredentials: Boolean): T {
+ mEnableCredentials = enableCredentials
+ return this as T
+ }
+
+ /**
+ * Set a custom layout for the AuthMethodPickerActivity screen.
+ * See [AuthMethodPickerLayout].
+ *
+ * @param authMethodPickerLayout custom layout descriptor object.
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun setAuthMethodPickerLayout(authMethodPickerLayout: AuthMethodPickerLayout): T {
+ mAuthMethodPickerLayout = authMethodPickerLayout
+ return this as T
+ }
+
+ /**
+ * Forces the sign-in method choice screen to always show, even if there is only
+ * a single provider configured.
+ *
+ * This is false by default.
+ *
+ * @param alwaysShow if true, force the sign-in choice screen to show.
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun setAlwaysShowSignInMethodScreen(alwaysShow: Boolean): T {
+ require(!(alwaysShow && mDefaultProvider != null)) {
+ "Can't show provider choice with a default provider."
+ }
+ mAlwaysShowProviderChoice = alwaysShow
+ return this as T
+ }
+
+ /**
+ * Enable or disables the orientation for small devices to be locked in
+ * Portrait orientation
+ *
+ * This is false by default.
+ *
+ * @param lockOrientation if true, force the activities to be in Portrait orientation.
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun setLockOrientation(lockOrientation: Boolean): T {
+ mLockOrientation = lockOrientation
+ return this as T
+ }
+
+ /**
+ * Set custom settings for the RecoverPasswordActivity.
+ *
+ * @param passwordSettings to allow additional state via a continue URL.
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun setResetPasswordSettings(passwordSettings: ActionCodeSettings): T {
+ mPasswordSettings = passwordSettings
+ return this as T
+ }
+
+ open fun build(): Intent {
+ if (mProviders.isEmpty()) {
+ mProviders.add(IdpConfig.EmailBuilder().build())
+ }
+
+ return KickoffActivity.createIntent(app.applicationContext, getFlowParams())
+ }
+
+ protected abstract fun getFlowParams(): FlowParameters
+ }
+
+ /**
+ * Builder for the intent to start the user authentication flow.
+ */
+ inner class SignInIntentBuilder : AuthIntentBuilder() {
+ private var mEmailLink: String? = null
+ private var mEnableAnonymousUpgrade = false
+
+ /**
+ * Specifies the email link to be used for sign in. When set, a sign in attempt will be
+ * made immediately.
+ */
+ fun setEmailLink(emailLink: String): SignInIntentBuilder {
+ mEmailLink = emailLink
+ return this
+ }
+
+ /**
+ * Enables upgrading anonymous accounts to full accounts during the sign-in flow.
+ * This is disabled by default.
+ *
+ * @throws IllegalStateException when you attempt to enable anonymous user upgrade
+ * without forcing the same device flow for email link sign in.
+ */
+ fun enableAnonymousUsersAutoUpgrade(): SignInIntentBuilder {
+ mEnableAnonymousUpgrade = true
+ validateEmailBuilderConfig()
+ return this
+ }
+
+ private fun validateEmailBuilderConfig() {
+ for (config in mProviders) {
+ if (config.getProviderId() == EMAIL_LINK_PROVIDER) {
+ val emailLinkForceSameDevice =
+ config.getParams().getBoolean(ExtraConstants.FORCE_SAME_DEVICE, true)
+ require(emailLinkForceSameDevice) {
+ "You must force the same device flow when using email link sign in with " +
+ "anonymous user upgrade"
+ }
+ }
+ }
+ }
+
+ override fun getFlowParams(): FlowParameters {
+ return FlowParameters(
+ mApp.name,
+ mProviders,
+ mDefaultProvider,
+ mTheme,
+ mLogo,
+ mTosUrl,
+ mPrivacyPolicyUrl,
+ mEnableCredentials,
+ mEnableAnonymousUpgrade,
+ mAlwaysShowProviderChoice,
+ mLockOrientation,
+ mEmailLink,
+ mPasswordSettings,
+ mAuthMethodPickerLayout
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/auth/src/main/java/com/firebase/ui/auth/ErrorCodes.java b/auth/src/main/java/com/firebase/ui/auth/ErrorCodes.java
deleted file mode 100644
index a39869d6e..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/ErrorCodes.java
+++ /dev/null
@@ -1,144 +0,0 @@
-package com.firebase.ui.auth;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-/**
- * Error codes for failed sign-in attempts.
- */
-public final class ErrorCodes {
- /**
- * An unknown error has occurred.
- */
- public static final int UNKNOWN_ERROR = 0;
- /**
- * Sign in failed due to lack of network connection.
- */
- public static final int NO_NETWORK = 1;
- /**
- * A required update to Play Services was cancelled by the user.
- */
- public static final int PLAY_SERVICES_UPDATE_CANCELLED = 2;
- /**
- * A sign-in operation couldn't be completed due to a developer error.
- */
- public static final int DEVELOPER_ERROR = 3;
- /**
- * An external sign-in provider error occurred.
- */
- public static final int PROVIDER_ERROR = 4;
- /**
- * Anonymous account linking failed.
- */
- public static final int ANONYMOUS_UPGRADE_MERGE_CONFLICT = 5;
- /**
- * Signing in with a different email in the WelcomeBackIdp flow or email link flow.
- */
- public static final int EMAIL_MISMATCH_ERROR = 6;
- /**
- * Attempting to sign in with an invalid email link.
- */
- public static final int INVALID_EMAIL_LINK_ERROR = 7;
-
- /**
- * Attempting to open an email link from a different device.
- */
- public static final int EMAIL_LINK_WRONG_DEVICE_ERROR = 8;
-
- /**
- * We need to prompt the user for their email.
- * */
- public static final int EMAIL_LINK_PROMPT_FOR_EMAIL_ERROR = 9;
-
- /**
- * Cross device linking flow - we need to ask the user if they want to continue linking or
- * just sign in.
- * */
- public static final int EMAIL_LINK_CROSS_DEVICE_LINKING_ERROR = 10;
-
- /**
- * Attempting to open an email link from the same device, with anonymous upgrade enabled,
- * but the underlying anonymous user has been changed.
- */
- public static final int EMAIL_LINK_DIFFERENT_ANONYMOUS_USER_ERROR = 11;
-
- /**
- * Attempting to auth with account that is currently disabled in the Firebase console.
- */
- public static final int ERROR_USER_DISABLED = 12;
-
- /**
- * Recoverable error occurred during the Generic IDP flow.
- */
- public static final int ERROR_GENERIC_IDP_RECOVERABLE_ERROR = 13;
-
- private ErrorCodes() {
- throw new AssertionError("No instance for you!");
- }
-
- @NonNull
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public static String toFriendlyMessage(@Code int code) {
- switch (code) {
- case UNKNOWN_ERROR:
- return "Unknown error";
- case NO_NETWORK:
- return "No internet connection";
- case PLAY_SERVICES_UPDATE_CANCELLED:
- return "Play Services update cancelled";
- case DEVELOPER_ERROR:
- return "Developer error";
- case PROVIDER_ERROR:
- return "Provider error";
- case ANONYMOUS_UPGRADE_MERGE_CONFLICT:
- return "User account merge conflict";
- case EMAIL_MISMATCH_ERROR:
- return "You are are attempting to sign in a different email than previously " +
- "provided";
- case INVALID_EMAIL_LINK_ERROR:
- return "You are are attempting to sign in with an invalid email link";
- case EMAIL_LINK_PROMPT_FOR_EMAIL_ERROR:
- return "Please enter your email to continue signing in";
- case EMAIL_LINK_WRONG_DEVICE_ERROR:
- return "You must open the email link on the same device.";
- case EMAIL_LINK_CROSS_DEVICE_LINKING_ERROR:
- return "You must determine if you want to continue linking or complete the sign in";
- case EMAIL_LINK_DIFFERENT_ANONYMOUS_USER_ERROR:
- return "The session associated with this sign-in request has either expired or " +
- "was cleared";
- case ERROR_USER_DISABLED:
- return "The user account has been disabled by an administrator.";
- case ERROR_GENERIC_IDP_RECOVERABLE_ERROR:
- return "Generic IDP recoverable error.";
- default:
- throw new IllegalArgumentException("Unknown code: " + code);
- }
- }
-
- /**
- * Valid codes that can be returned from {@link FirebaseUiException#getErrorCode()}.
- */
- @IntDef({
- UNKNOWN_ERROR,
- NO_NETWORK,
- PLAY_SERVICES_UPDATE_CANCELLED,
- DEVELOPER_ERROR,
- PROVIDER_ERROR,
- ANONYMOUS_UPGRADE_MERGE_CONFLICT,
- EMAIL_MISMATCH_ERROR,
- INVALID_EMAIL_LINK_ERROR,
- EMAIL_LINK_WRONG_DEVICE_ERROR,
- EMAIL_LINK_PROMPT_FOR_EMAIL_ERROR,
- EMAIL_LINK_CROSS_DEVICE_LINKING_ERROR,
- EMAIL_LINK_DIFFERENT_ANONYMOUS_USER_ERROR,
- ERROR_USER_DISABLED,
- ERROR_GENERIC_IDP_RECOVERABLE_ERROR
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface Code {
- }
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ErrorCodes.kt b/auth/src/main/java/com/firebase/ui/auth/ErrorCodes.kt
new file mode 100644
index 000000000..a37746277
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ErrorCodes.kt
@@ -0,0 +1,124 @@
+package com.firebase.ui.auth
+
+import androidx.annotation.RestrictTo
+import androidx.annotation.IntDef
+import kotlin.jvm.JvmStatic
+
+/**
+ * Error codes for failed sign-in attempts.
+ */
+object ErrorCodes {
+ /**
+ * Valid codes that can be returned from FirebaseUiException.getErrorCode().
+ */
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(
+ UNKNOWN_ERROR,
+ NO_NETWORK,
+ PLAY_SERVICES_UPDATE_CANCELLED,
+ DEVELOPER_ERROR,
+ PROVIDER_ERROR,
+ ANONYMOUS_UPGRADE_MERGE_CONFLICT,
+ EMAIL_MISMATCH_ERROR,
+ INVALID_EMAIL_LINK_ERROR,
+ EMAIL_LINK_WRONG_DEVICE_ERROR,
+ EMAIL_LINK_PROMPT_FOR_EMAIL_ERROR,
+ EMAIL_LINK_CROSS_DEVICE_LINKING_ERROR,
+ EMAIL_LINK_DIFFERENT_ANONYMOUS_USER_ERROR,
+ ERROR_USER_DISABLED,
+ ERROR_GENERIC_IDP_RECOVERABLE_ERROR
+ )
+ annotation class Code
+
+ /**
+ * An unknown error has occurred.
+ */
+ const val UNKNOWN_ERROR = 0
+
+ /**
+ * Sign in failed due to lack of network connection.
+ */
+ const val NO_NETWORK = 1
+
+ /**
+ * A required update to Play Services was cancelled by the user.
+ */
+ const val PLAY_SERVICES_UPDATE_CANCELLED = 2
+
+ /**
+ * A sign-in operation couldn't be completed due to a developer error.
+ */
+ const val DEVELOPER_ERROR = 3
+
+ /**
+ * An external sign-in provider error occurred.
+ */
+ const val PROVIDER_ERROR = 4
+
+ /**
+ * Anonymous account linking failed.
+ */
+ const val ANONYMOUS_UPGRADE_MERGE_CONFLICT = 5
+
+ /**
+ * Signing in with a different email in the WelcomeBackIdp flow or email link flow.
+ */
+ const val EMAIL_MISMATCH_ERROR = 6
+
+ /**
+ * Attempting to sign in with an invalid email link.
+ */
+ const val INVALID_EMAIL_LINK_ERROR = 7
+
+ /**
+ * Attempting to open an email link from a different device.
+ */
+ const val EMAIL_LINK_WRONG_DEVICE_ERROR = 8
+
+ /**
+ * We need to prompt the user for their email.
+ */
+ const val EMAIL_LINK_PROMPT_FOR_EMAIL_ERROR = 9
+
+ /**
+ * Cross device linking flow - we need to ask the user if they want to continue linking or
+ * just sign in.
+ */
+ const val EMAIL_LINK_CROSS_DEVICE_LINKING_ERROR = 10
+
+ /**
+ * Attempting to open an email link from the same device, with anonymous upgrade enabled,
+ * but the underlying anonymous user has been changed.
+ */
+ const val EMAIL_LINK_DIFFERENT_ANONYMOUS_USER_ERROR = 11
+
+ /**
+ * Attempting to auth with account that is currently disabled in the Firebase console.
+ */
+ const val ERROR_USER_DISABLED = 12
+
+ /**
+ * Recoverable error occurred during the Generic IDP flow.
+ */
+ const val ERROR_GENERIC_IDP_RECOVERABLE_ERROR = 13
+
+ @JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ fun toFriendlyMessage(@Code code: Int): String = when (code) {
+ UNKNOWN_ERROR -> "Unknown error"
+ NO_NETWORK -> "No internet connection"
+ PLAY_SERVICES_UPDATE_CANCELLED -> "Play Services update cancelled"
+ DEVELOPER_ERROR -> "Developer error"
+ PROVIDER_ERROR -> "Provider error"
+ ANONYMOUS_UPGRADE_MERGE_CONFLICT -> "User account merge conflict"
+ EMAIL_MISMATCH_ERROR -> "You are are attempting to sign in a different email than previously provided"
+ INVALID_EMAIL_LINK_ERROR -> "You are are attempting to sign in with an invalid email link"
+ EMAIL_LINK_PROMPT_FOR_EMAIL_ERROR -> "Please enter your email to continue signing in"
+ EMAIL_LINK_WRONG_DEVICE_ERROR -> "You must open the email link on the same device."
+ EMAIL_LINK_CROSS_DEVICE_LINKING_ERROR -> "You must determine if you want to continue linking or complete the sign in"
+ EMAIL_LINK_DIFFERENT_ANONYMOUS_USER_ERROR -> "The session associated with this sign-in request has either expired or was cleared"
+ ERROR_USER_DISABLED -> "The user account has been disabled by an administrator."
+ ERROR_GENERIC_IDP_RECOVERABLE_ERROR -> "Generic IDP recoverable error."
+ else -> throw IllegalArgumentException("Unknown code: $code")
+ }
+}
\ No newline at end of file