diff --git a/auth/src/main/java/com/firebase/ui/auth/AuthMethodPickerLayout.java b/auth/src/main/java/com/firebase/ui/auth/AuthMethodPickerLayout.java
index 3644f2bcc..0e10eb1c4 100644
--- a/auth/src/main/java/com/firebase/ui/auth/AuthMethodPickerLayout.java
+++ b/auth/src/main/java/com/firebase/ui/auth/AuthMethodPickerLayout.java
@@ -198,9 +198,11 @@ public AuthMethodPickerLayout build() {
}
for (String key : providersMapping.keySet()) {
- if (!AuthUI.SUPPORTED_PROVIDERS.contains(key)
- && !AuthUI.SUPPORTED_OAUTH_PROVIDERS.contains(key)) {
- throw new IllegalArgumentException("Unknown provider: " + key);
+ if (key == null) continue;
+ if (!AuthUI.isSupportedProvider(key)
+ && !AuthUI.isSupportedOAuthProvider(key)) {
+ throw new IllegalStateException(
+ "Unknown provider: " + key);
}
}
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..5dd9e0d0f
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/AuthUI.kt
@@ -0,0 +1,889 @@
+/*
+ * 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.util.Log
+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
+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.GoogleSignInOptions
+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.EmailAuthProvider
+import com.google.firebase.auth.FacebookAuthProvider
+import com.google.firebase.auth.FirebaseAuth
+import com.google.firebase.auth.FirebaseAuthInvalidUserException
+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 java.util.IdentityHashMap
+import java.util.*
+import com.google.android.gms.common.api.CommonStatusCodes
+
+class AuthUI private constructor(private val mApp: FirebaseApp) {
+
+ private val mAuth: FirebaseAuth = FirebaseAuth.getInstance(mApp)
+ private var mEmulatorHost: String? = null
+ private var mEmulatorPort = -1
+
+ init {
+ try {
+ mAuth.setFirebaseUIVersion(BuildConfig.VERSION_NAME)
+ } catch (e: Exception) {
+ Log.e(TAG, "Couldn't set the FUI version.", e)
+ }
+ mAuth.useAppLanguage()
+ }
+
+ fun getApp(): FirebaseApp = mApp
+
+ fun getAuth(): FirebaseAuth = mAuth
+
+ 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
+ }
+ }
+
+ fun delete(context: Context): Task {
+ val currentUser = mAuth.currentUser
+ if (currentUser == null) {
+ return Tasks.forException(
+ FirebaseAuthInvalidUserException(
+ CommonStatusCodes.SIGN_IN_REQUIRED.toString(),
+ "No currently signed in user."
+ )
+ )
+ }
+ return signOutIdps(context).continueWithTask { task ->
+ task.result // propagate exception if any.
+ currentUser.delete()
+ }
+ }
+
+ fun useEmulator(host: String, port: Int) {
+ 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)
+ 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)
+ }
+ }
+
+ fun createSignInIntentBuilder(): SignInIntentBuilder {
+ return 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(AnnotationRetention.SOURCE)
+ annotation class SupportedProvider
+
+ class IdpConfig private constructor(private val mProviderId: String, private val mParams: Bundle) :
+ Parcelable {
+
+ val providerId: String
+ get() = mProviderId
+
+ 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 {
+ return IdpConfig(parcel)
+ }
+
+ override fun newArray(size: Int): Array = arrayOfNulls(size)
+ }
+ }
+
+ private constructor(parcel: Parcel) : this(
+ parcel.readString() ?: "",
+ parcel.readBundle(IdpConfig::class.java.classLoader) ?: Bundle()
+ )
+
+ open class Builder(@NonNull @SupportedProvider providerId: String) {
+ protected val mParams: Bundle = Bundle()
+ protected var mProviderId: String = providerId
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ protected fun getParams(): Bundle = mParams
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ protected fun setProviderId(providerId: String) {
+ mProviderId = providerId
+ }
+
+ @CallSuper
+ open fun build(): IdpConfig = IdpConfig(mProviderId, mParams)
+ }
+
+ class EmailBuilder : Builder(EmailAuthProvider.PROVIDER_ID) {
+ fun setAllowNewAccounts(allow: Boolean): EmailBuilder {
+ getParams().putBoolean(ExtraConstants.ALLOW_NEW_EMAILS, allow)
+ return this
+ }
+
+ fun setRequireName(requireName: Boolean): EmailBuilder {
+ getParams().putBoolean(ExtraConstants.REQUIRE_NAME, requireName)
+ return this
+ }
+
+ fun enableEmailLinkSignIn(): EmailBuilder {
+ setProviderId(EMAIL_LINK_PROVIDER)
+ return this
+ }
+
+ fun setActionCodeSettings(actionCodeSettings: ActionCodeSettings): EmailBuilder {
+ getParams().putParcelable(ExtraConstants.ACTION_CODE_SETTINGS, actionCodeSettings)
+ return this
+ }
+
+ fun setForceSameDevice(): EmailBuilder {
+ getParams().putBoolean(ExtraConstants.FORCE_SAME_DEVICE, true)
+ return this
+ }
+
+ fun setDefaultEmail(email: String): EmailBuilder {
+ getParams().putString(ExtraConstants.DEFAULT_EMAIL, email)
+ return this
+ }
+
+ override fun build(): IdpConfig {
+ if (mProviderId == EMAIL_LINK_PROVIDER) {
+ val actionCodeSettings: ActionCodeSettings? =
+ getParams().getParcelable(ExtraConstants.ACTION_CODE_SETTINGS)
+ Preconditions.checkNotNull(
+ actionCodeSettings,
+ "ActionCodeSettings cannot be null when using email link sign in."
+ )
+ if (!actionCodeSettings!!.canHandleCodeInApp()) {
+ throw IllegalStateException(
+ "You must set canHandleCodeInApp in your ActionCodeSettings to true for Email-Link Sign-in."
+ )
+ }
+ }
+ return super.build()
+ }
+ }
+
+ class PhoneBuilder : Builder(PhoneAuthProvider.PROVIDER_ID) {
+ fun setDefaultNumber(number: String): PhoneBuilder {
+ Preconditions.checkUnset(
+ getParams(),
+ "Cannot overwrite previously set phone number",
+ ExtraConstants.PHONE,
+ ExtraConstants.COUNTRY_ISO,
+ ExtraConstants.NATIONAL_NUMBER
+ )
+ if (!PhoneNumberUtils.isValid(number)) {
+ throw IllegalStateException("Invalid phone number: $number")
+ }
+ getParams().putString(ExtraConstants.PHONE, number)
+ return this
+ }
+
+ fun setDefaultNumber(iso: String, number: String): PhoneBuilder {
+ Preconditions.checkUnset(
+ getParams(),
+ "Cannot overwrite previously set phone number",
+ ExtraConstants.PHONE,
+ ExtraConstants.COUNTRY_ISO,
+ ExtraConstants.NATIONAL_NUMBER
+ )
+ if (!PhoneNumberUtils.isValidIso(iso)) {
+ throw IllegalStateException("Invalid country iso: $iso")
+ }
+ getParams().putString(ExtraConstants.COUNTRY_ISO, iso)
+ getParams().putString(ExtraConstants.NATIONAL_NUMBER, number)
+ return this
+ }
+
+ fun setDefaultCountryIso(iso: String): PhoneBuilder {
+ Preconditions.checkUnset(
+ getParams(),
+ "Cannot overwrite previously set phone number",
+ ExtraConstants.PHONE,
+ ExtraConstants.COUNTRY_ISO,
+ ExtraConstants.NATIONAL_NUMBER
+ )
+ if (!PhoneNumberUtils.isValidIso(iso)) {
+ throw IllegalStateException("Invalid country iso: $iso")
+ }
+ getParams().putString(
+ ExtraConstants.COUNTRY_ISO,
+ iso.uppercase(Locale.getDefault())
+ )
+ return this
+ }
+
+ fun setAllowedCountries(countries: List): PhoneBuilder {
+ if (getParams().containsKey(ExtraConstants.BLOCKLISTED_COUNTRIES)) {
+ throw IllegalStateException(
+ "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."
+ Preconditions.checkNotNull(countries, String.format(message, "null"))
+ Preconditions.checkArgument(countries.isNotEmpty(), String.format(message, "empty"))
+ addCountriesToBundle(countries, ExtraConstants.ALLOWLISTED_COUNTRIES)
+ return this
+ }
+
+ fun setBlockedCountries(countries: List): PhoneBuilder {
+ if (getParams().containsKey(ExtraConstants.ALLOWLISTED_COUNTRIES)) {
+ throw IllegalStateException(
+ "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."
+ Preconditions.checkNotNull(countries, String.format(message, "null"))
+ Preconditions.checkArgument(countries.isNotEmpty(), String.format(message, "empty"))
+ addCountriesToBundle(countries, ExtraConstants.BLOCKLISTED_COUNTRIES)
+ return this
+ }
+
+ override fun build(): IdpConfig {
+ validateInputs()
+ return super.build()
+ }
+
+ private fun addCountriesToBundle(countryIsos: List, countryIsoType: String) {
+ val uppercaseCodes = ArrayList()
+ for (code in countryIsos) {
+ uppercaseCodes.add(code.uppercase(Locale.getDefault()))
+ }
+ getParams().putStringArrayList(countryIsoType, uppercaseCodes)
+ }
+
+ private fun validateInputs() {
+ val allowedCountries = getParams().getStringArrayList(ExtraConstants.ALLOWLISTED_COUNTRIES)
+ val blockedCountries = getParams().getStringArrayList(ExtraConstants.BLOCKLISTED_COUNTRIES)
+ if (allowedCountries != null && blockedCountries != null) {
+ throw 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 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 (getParams().containsKey(ExtraConstants.COUNTRY_ISO) ||
+ getParams().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 isoUpper = iso.uppercase(Locale.getDefault())
+ for (code in codes) {
+ if (PhoneNumberUtils.isValidIso(code)) {
+ if (code.equals(isoUpper, ignoreCase = true)) {
+ return true
+ }
+ } else {
+ val isos = PhoneNumberUtils.getCountryIsosFromCountryCode(code)
+ if (isos?.contains(isoUpper) == true) {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ private fun getPhoneIsosFromCode(): List {
+ val isos = ArrayList()
+ val phone = getParams().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? {
+ return if (getParams().containsKey(ExtraConstants.COUNTRY_ISO))
+ getParams().getString(ExtraConstants.COUNTRY_ISO)
+ else null
+ }
+ }
+
+ 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
+ )
+ }
+
+ 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())
+ }
+
+ fun setSignInOptions(options: GoogleSignInOptions): GoogleBuilder {
+ Preconditions.checkUnset(
+ getParams(),
+ "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)
+ }
+ var hasEmailScope = false
+ for (s in options.scopes) {
+ if ("email" == s.scopeUri) {
+ 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
+ }
+
+ override fun build(): IdpConfig {
+ if (!getParams().containsKey(ExtraConstants.GOOGLE_SIGN_IN_OPTIONS)) {
+ validateWebClientId()
+ setScopes(Collections.emptyList())
+ }
+ return super.build()
+ }
+ }
+
+ class FacebookBuilder : Builder(FacebookAuthProvider.PROVIDER_ID) {
+ 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.")
+ }
+ }
+
+ fun setPermissions(permissions: List): FacebookBuilder {
+ getParams().putStringArrayList(
+ ExtraConstants.FACEBOOK_PERMISSIONS,
+ ArrayList(permissions)
+ )
+ return this
+ }
+ }
+
+ class AnonymousBuilder : Builder(ANONYMOUS_PROVIDER)
+
+ class TwitterBuilder : GenericOAuthProviderBuilder(
+ TwitterAuthProvider.PROVIDER_ID,
+ "Twitter",
+ R.layout.fui_idp_button_twitter
+ )
+
+ class GitHubBuilder : GenericOAuthProviderBuilder(
+ GithubAuthProvider.PROVIDER_ID,
+ "Github",
+ R.layout.fui_idp_button_github
+ ) {
+ @Deprecated("Please use setScopes(List) instead.")
+ fun setPermissions(permissions: List): GitHubBuilder {
+ setScopes(permissions)
+ return this
+ }
+ }
+
+ class AppleBuilder : GenericOAuthProviderBuilder(
+ APPLE_PROVIDER,
+ "Apple",
+ R.layout.fui_idp_button_apple
+ )
+
+ class MicrosoftBuilder : GenericOAuthProviderBuilder(
+ MICROSOFT_PROVIDER,
+ "Microsoft",
+ R.layout.fui_idp_button_microsoft
+ )
+
+ class YahooBuilder : GenericOAuthProviderBuilder(
+ YAHOO_PROVIDER,
+ "Yahoo",
+ R.layout.fui_idp_button_yahoo
+ )
+
+ open class GenericOAuthProviderBuilder(providerId: String, providerName: String, buttonId: Int) :
+ Builder(providerId) {
+ init {
+ 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)
+ }
+
+ fun setScopes(scopes: List): GenericOAuthProviderBuilder {
+ getParams().putStringArrayList(
+ ExtraConstants.GENERIC_OAUTH_SCOPES,
+ ArrayList(scopes)
+ )
+ return this
+ }
+
+ fun setCustomParameters(customParameters: Map): GenericOAuthProviderBuilder {
+ getParams().putSerializable(
+ ExtraConstants.GENERIC_OAUTH_CUSTOM_PARAMETERS,
+ HashMap(customParameters)
+ )
+ return this
+ }
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ public abstract inner class AuthIntentBuilder> {
+ val mProviders: MutableList = ArrayList()
+ var mDefaultProvider: IdpConfig? = null
+ var mLogo: Int = NO_LOGO
+ var mTheme: Int = getDefaultTheme()
+ var mTosUrl: String? = null
+ var mPrivacyPolicyUrl: String? = null
+ var mAlwaysShowProviderChoice = false
+ var mLockOrientation = false
+ var mEnableCredentials = true
+ var mAuthMethodPickerLayout: AuthMethodPickerLayout? = null
+ var mPasswordSettings: ActionCodeSettings? = null
+
+ fun setTheme(@StyleRes theme: Int): T {
+ mTheme = Preconditions.checkValidStyle(
+ mApp.applicationContext,
+ theme,
+ "theme identifier is unknown or not a style definition"
+ )
+ return this as T
+ }
+
+ fun setLogo(@DrawableRes logo: Int): T {
+ mLogo = logo
+ return this as T
+ }
+
+ @Deprecated("Please use setTosAndPrivacyPolicyUrls(String, String)")
+ fun setTosUrl(tosUrl: String?): T {
+ mTosUrl = tosUrl
+ return this as T
+ }
+
+ @Deprecated("Please use setTosAndPrivacyPolicyUrls(String, String)")
+ fun setPrivacyPolicyUrl(privacyPolicyUrl: String?): T {
+ mPrivacyPolicyUrl = privacyPolicyUrl
+ return this as T
+ }
+
+ fun setTosAndPrivacyPolicyUrls(tosUrl: String, privacyPolicyUrl: String): T {
+ Preconditions.checkNotNull(tosUrl, "tosUrl cannot be null")
+ Preconditions.checkNotNull(privacyPolicyUrl, "privacyPolicyUrl cannot be null")
+ mTosUrl = tosUrl
+ mPrivacyPolicyUrl = privacyPolicyUrl
+ return this as T
+ }
+
+ fun setAvailableProviders(idpConfigs: List): T {
+ Preconditions.checkNotNull(idpConfigs, "idpConfigs cannot be null")
+ if (idpConfigs.size == 1 && idpConfigs[0].providerId == ANONYMOUS_PROVIDER) {
+ throw 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 (config in idpConfigs) {
+ if (mProviders.contains(config)) {
+ throw IllegalArgumentException("Each provider can only be set once. " +
+ "${config.providerId} was set twice.")
+ } else {
+ mProviders.add(config)
+ }
+ }
+ return this as T
+ }
+
+ fun setDefaultProvider(config: IdpConfig?): T {
+ if (config != null) {
+ if (!mProviders.contains(config)) {
+ throw IllegalStateException("Default provider not in available providers list.")
+ }
+ if (mAlwaysShowProviderChoice) {
+ throw IllegalStateException("Can't set default provider and always show provider choice.")
+ }
+ }
+ mDefaultProvider = config
+ return this as T
+ }
+
+ fun setCredentialManagerEnabled(enableCredentials: Boolean): T {
+ mEnableCredentials = enableCredentials
+ return this as T
+ }
+
+ fun setAuthMethodPickerLayout(authMethodPickerLayout: AuthMethodPickerLayout): T {
+ mAuthMethodPickerLayout = authMethodPickerLayout
+ return this as T
+ }
+
+ fun setAlwaysShowSignInMethodScreen(alwaysShow: Boolean): T {
+ if (alwaysShow && mDefaultProvider != null) {
+ throw IllegalStateException("Can't show provider choice with a default provider.")
+ }
+ mAlwaysShowProviderChoice = alwaysShow
+ return this as T
+ }
+
+ fun setLockOrientation(lockOrientation: Boolean): T {
+ mLockOrientation = lockOrientation
+ return this as T
+ }
+
+ fun setResetPasswordSettings(passwordSettings: ActionCodeSettings): T {
+ mPasswordSettings = passwordSettings
+ return this as T
+ }
+
+ @CallSuper
+ open fun build(): Intent {
+ if (mProviders.isEmpty()) {
+ mProviders.add(IdpConfig.EmailBuilder().build())
+ }
+ return KickoffActivity.createIntent(mApp.applicationContext, getFlowParams())
+ }
+
+ protected abstract fun getFlowParams(): FlowParameters
+ }
+
+ inner class SignInIntentBuilder : AuthIntentBuilder() {
+ private var mEmailLink: String? = null
+ private var mEnableAnonymousUpgrade = false
+
+ fun setEmailLink(emailLink: String): SignInIntentBuilder {
+ mEmailLink = emailLink
+ return this
+ }
+
+ fun enableAnonymousUsersAutoUpgrade(): SignInIntentBuilder {
+ mEnableAnonymousUpgrade = true
+ validateEmailBuilderConfig()
+ return this
+ }
+
+ private fun validateEmailBuilderConfig() {
+ for (config in mProviders) {
+ if (config.providerId == EMAIL_LINK_PROVIDER) {
+ val emailLinkForceSameDevice =
+ config.getParams().getBoolean(ExtraConstants.FORCE_SAME_DEVICE, true)
+ if (!emailLinkForceSameDevice) {
+ throw IllegalStateException(
+ "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
+ )
+ }
+ }
+
+ companion object {
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ const val TAG = "AuthUI"
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ const val ANONYMOUS_PROVIDER = "anonymous"
+ const val EMAIL_LINK_PROVIDER = EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD
+
+ const val MICROSOFT_PROVIDER = "microsoft.com"
+ const val YAHOO_PROVIDER = "yahoo.com"
+ const val APPLE_PROVIDER = "apple.com"
+
+ const val NO_LOGO = -1
+
+ @JvmField
+ public val SUPPORTED_PROVIDERS: Set = setOf(
+ GoogleAuthProvider.PROVIDER_ID,
+ FacebookAuthProvider.PROVIDER_ID,
+ TwitterAuthProvider.PROVIDER_ID,
+ GithubAuthProvider.PROVIDER_ID,
+ EmailAuthProvider.PROVIDER_ID,
+ PhoneAuthProvider.PROVIDER_ID,
+ ANONYMOUS_PROVIDER,
+ EMAIL_LINK_PROVIDER
+ )
+
+ @JvmField
+ public val SUPPORTED_OAUTH_PROVIDERS: Set = setOf(
+ MICROSOFT_PROVIDER,
+ YAHOO_PROVIDER,
+ APPLE_PROVIDER,
+ TwitterAuthProvider.PROVIDER_ID,
+ GithubAuthProvider.PROVIDER_ID
+ )
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ val SOCIAL_PROVIDERS: Set = setOf(
+ GoogleAuthProvider.PROVIDER_ID,
+ FacebookAuthProvider.PROVIDER_ID
+ )
+
+ @JvmStatic
+ fun isSocialProvider(providerId: String): Boolean {
+ return SOCIAL_PROVIDERS.contains(providerId)
+ }
+
+ @JvmStatic
+ fun isSupportedProvider(providerId: String): Boolean {
+ return SUPPORTED_PROVIDERS.contains(providerId)
+ }
+
+ @JvmStatic
+ fun isSupportedOAuthProvider(providerId: String): Boolean {
+ return SUPPORTED_OAUTH_PROVIDERS.contains(providerId)
+ }
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ const val UNCONFIGURED_CONFIG_VALUE = "CHANGE-ME"
+
+ private val INSTANCES: IdentityHashMap = IdentityHashMap()
+
+ @JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ fun getApplicationContext(): Context {
+ return sApplicationContext ?: throw IllegalStateException("Application context not set")
+ }
+
+ @JvmStatic
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ fun setApplicationContext(context: Context) {
+ sApplicationContext = Preconditions.checkNotNull(context, "App context cannot be null.").applicationContext
+ }
+
+ @JvmStatic
+ fun getInstance(): AuthUI {
+ return getInstance(FirebaseApp.getInstance())
+ }
+
+ @JvmStatic
+ fun getInstance(appName: String): AuthUI {
+ return getInstance(FirebaseApp.getInstance(appName))
+ }
+
+ @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
+ }
+ }
+
+ @JvmStatic
+ fun canHandleIntent(intent: Intent?): Boolean {
+ if (intent == null || intent.data == null) {
+ return false
+ }
+ val link = intent.data.toString()
+ return FirebaseAuth.getInstance().isSignInWithEmailLink(link)
+ }
+
+ @StyleRes
+ @JvmStatic
+ fun getDefaultTheme(): Int = R.style.FirebaseUI_DefaultMaterialTheme
+
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ private var sApplicationContext: Context? = null
+ }
+}
\ 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
diff --git a/auth/src/main/java/com/firebase/ui/auth/IdpResponse.java b/auth/src/main/java/com/firebase/ui/auth/IdpResponse.java
index 337188acc..d658c4829 100644
--- a/auth/src/main/java/com/firebase/ui/auth/IdpResponse.java
+++ b/auth/src/main/java/com/firebase/ui/auth/IdpResponse.java
@@ -385,7 +385,7 @@ public IdpResponse build() {
String providerId = mUser.getProviderId();
- if (AuthUI.SOCIAL_PROVIDERS.contains(providerId) && TextUtils.isEmpty(mToken)) {
+ if (AuthUI.isSocialProvider(providerId) && TextUtils.isEmpty(mToken)) {
throw new IllegalStateException(
"Token cannot be null when using a non-email provider.");
}
diff --git a/auth/src/main/java/com/firebase/ui/auth/data/client/AuthUiInitProvider.java b/auth/src/main/java/com/firebase/ui/auth/data/client/AuthUiInitProvider.java
deleted file mode 100644
index d90e875d6..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/data/client/AuthUiInitProvider.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.firebase.ui.auth.data.client;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.ProviderInfo;
-import android.database.Cursor;
-import android.net.Uri;
-
-import com.firebase.ui.auth.AuthUI;
-import com.firebase.ui.auth.util.Preconditions;
-
-import androidx.annotation.RestrictTo;
-
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class AuthUiInitProvider extends ContentProvider {
- @Override
- public void attachInfo(Context context, ProviderInfo info) {
- Preconditions.checkNotNull(info, "AuthUiInitProvider ProviderInfo cannot be null.");
- if ("com.firebase.ui.auth.authuiinitprovider".equals(info.authority)) {
- throw new IllegalStateException("Incorrect provider authority in manifest. Most" +
- " likely due to a missing applicationId variable in application's build.gradle.");
- } else {
- super.attachInfo(context, info);
- }
- }
-
- @Override
- public boolean onCreate() {
- AuthUI.setApplicationContext(getContext());
- return false;
- }
-
- @Override
- public Cursor query(Uri uri,
- String[] projection,
- String selection,
- String[] selectionArgs,
- String sortOrder) {
- return null;
- }
-
- @Override
- public String getType(Uri uri) {
- return null;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- return null;
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- return 0;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- return 0;
- }
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/data/client/AuthUiInitProvider.kt b/auth/src/main/java/com/firebase/ui/auth/data/client/AuthUiInitProvider.kt
new file mode 100644
index 000000000..028659082
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/data/client/AuthUiInitProvider.kt
@@ -0,0 +1,54 @@
+package com.firebase.ui.auth.data.client
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.content.pm.ProviderInfo
+import android.database.Cursor
+import android.net.Uri
+import com.firebase.ui.auth.AuthUI
+import com.firebase.ui.auth.util.Preconditions
+import androidx.annotation.RestrictTo
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class AuthUiInitProvider : ContentProvider() {
+
+ override fun attachInfo(context: Context, info: ProviderInfo) {
+ Preconditions.checkNotNull(info, "AuthUiInitProvider ProviderInfo cannot be null.")
+ if ("com.firebase.ui.auth.authuiinitprovider" == info.authority) {
+ throw IllegalStateException(
+ "Incorrect provider authority in manifest. Most likely due to a missing " +
+ "applicationId variable in application's build.gradle."
+ )
+ } else {
+ super.attachInfo(context, info)
+ }
+ }
+
+ override fun onCreate(): Boolean {
+ val context = context ?: throw IllegalStateException("Context cannot be null")
+ AuthUI.setApplicationContext(context)
+ return false
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array?,
+ selection: String?,
+ selectionArgs: Array?,
+ sortOrder: String?
+ ): Cursor? = null
+
+ override fun getType(uri: Uri): String? = null
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? = null
+
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 0
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array?
+ ): Int = 0
+}
\ No newline at end of file
diff --git a/auth/src/main/java/com/firebase/ui/auth/data/model/CountryInfo.java b/auth/src/main/java/com/firebase/ui/auth/data/model/CountryInfo.java
deleted file mode 100644
index d245146eb..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/data/model/CountryInfo.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2015 Twitter, Inc.
- *
- * 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.
- *
- * Modifications copyright (C) 2017 Google Inc
- *
- */
-package com.firebase.ui.auth.data.model;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.text.Collator;
-import java.util.Locale;
-
-import androidx.annotation.RestrictTo;
-
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public final class CountryInfo implements Comparable, Parcelable {
-
- public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
- @Override
- public CountryInfo createFromParcel(Parcel source) {
- return new CountryInfo(source);
- }
-
- @Override
- public CountryInfo[] newArray(int size) {
- return new CountryInfo[size];
- }
- };
-
- private final Collator mCollator;
- private final Locale mLocale;
- private final int mCountryCode;
-
- public CountryInfo(Locale locale, int countryCode) {
- mCollator = Collator.getInstance(Locale.getDefault());
- mCollator.setStrength(Collator.PRIMARY);
- mLocale = locale;
- mCountryCode = countryCode;
- }
-
- protected CountryInfo(Parcel in) {
- mCollator = Collator.getInstance(Locale.getDefault());
- mCollator.setStrength(Collator.PRIMARY);
-
- mLocale = (Locale) in.readSerializable();
- mCountryCode = in.readInt();
- }
-
- public static String localeToEmoji(Locale locale) {
- String countryCode = locale.getCountry();
- // 0x41 is Letter A
- // 0x1F1E6 is Regional Indicator Symbol Letter A
- // Example :
- // firstLetter U => 20 + 0x1F1E6
- // secondLetter S => 18 + 0x1F1E6
- // See: https://en.wikipedia.org/wiki/Regional_Indicator_Symbol
- int firstLetter = Character.codePointAt(countryCode, 0) - 0x41 + 0x1F1E6;
- int secondLetter = Character.codePointAt(countryCode, 1) - 0x41 + 0x1F1E6;
- return new String(Character.toChars(firstLetter)) + new String(Character.toChars
- (secondLetter));
- }
-
- public Locale getLocale() {
- return mLocale;
- }
-
- public int getCountryCode() {
- return mCountryCode;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- final CountryInfo that = (CountryInfo) o;
-
- return mCountryCode == that.mCountryCode
- && (mLocale != null ? mLocale.equals(that.mLocale) : that.mLocale == null);
- }
-
- @Override
- public int hashCode() {
- int result = mLocale != null ? mLocale.hashCode() : 0;
- result = 31 * result + mCountryCode;
- return result;
- }
-
- @Override
- public String toString() {
- return localeToEmoji(mLocale) + " " + mLocale.getDisplayCountry() + " +" + mCountryCode;
- }
-
- public String toShortString() {
- return localeToEmoji(mLocale) + " +" + mCountryCode;
- }
-
- @Override
- public int compareTo(CountryInfo info) {
- Locale defaultLocale = Locale.getDefault();
- return mCollator.compare(
- mLocale.getDisplayCountry().toUpperCase(defaultLocale),
- info.mLocale.getDisplayCountry().toUpperCase(defaultLocale));
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeSerializable(mLocale);
- dest.writeInt(mCountryCode);
- }
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/data/model/CountryInfo.kt b/auth/src/main/java/com/firebase/ui/auth/data/model/CountryInfo.kt
new file mode 100644
index 000000000..710fa79d9
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/data/model/CountryInfo.kt
@@ -0,0 +1,75 @@
+package com.firebase.ui.auth.data.model
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.annotation.RestrictTo
+import java.text.Collator
+import java.util.Locale
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class CountryInfo(val locale: Locale?, val countryCode: Int) : Comparable, Parcelable {
+
+ // Use a collator initialized to the default locale.
+ private val collator: Collator = Collator.getInstance(Locale.getDefault()).apply {
+ strength = Collator.PRIMARY
+ }
+
+ companion object {
+ @JvmField
+ val CREATOR: Parcelable.Creator = object : Parcelable.Creator {
+ override fun createFromParcel(source: Parcel): CountryInfo = CountryInfo(source)
+ override fun newArray(size: Int): Array = arrayOfNulls(size)
+ }
+
+ fun localeToEmoji(locale: Locale?): String {
+ if (locale == null) return ""
+ val countryCode = locale.country
+ // 0x41 is Letter A, 0x1F1E6 is Regional Indicator Symbol Letter A.
+ // For example, for "US": 'U' => (0x55 - 0x41) + 0x1F1E6, 'S' => (0x53 - 0x41) + 0x1F1E6.
+ val firstLetter = Character.codePointAt(countryCode, 0) - 0x41 + 0x1F1E6
+ val secondLetter = Character.codePointAt(countryCode, 1) - 0x41 + 0x1F1E6
+ return String(Character.toChars(firstLetter)) + String(Character.toChars(secondLetter))
+ }
+ }
+
+ // Secondary constructor to recreate from a Parcel.
+ constructor(parcel: Parcel) : this(
+ parcel.readSerializable() as? Locale,
+ parcel.readInt()
+ )
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is CountryInfo) return false
+ return countryCode == other.countryCode && locale == other.locale
+ }
+
+ override fun hashCode(): Int {
+ var result = locale?.hashCode() ?: 0
+ result = 31 * result + countryCode
+ return result
+ }
+
+ override fun toString(): String {
+ return "${localeToEmoji(locale)} ${locale?.displayCountry ?: ""} +$countryCode"
+ }
+
+ fun toShortString(): String {
+ return "${localeToEmoji(locale)} +$countryCode"
+ }
+
+ override fun compareTo(other: CountryInfo): Int {
+ val defaultLocale = Locale.getDefault()
+ return collator.compare(
+ locale?.displayCountry?.uppercase(defaultLocale) ?: "",
+ other.locale?.displayCountry?.uppercase(defaultLocale) ?: ""
+ )
+ }
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeSerializable(locale)
+ dest.writeInt(countryCode)
+ }
+}
\ No newline at end of file
diff --git a/auth/src/main/java/com/firebase/ui/auth/data/model/FirebaseAuthUIAuthenticationResult.java b/auth/src/main/java/com/firebase/ui/auth/data/model/FirebaseAuthUIAuthenticationResult.java
deleted file mode 100644
index eea7deabc..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/data/model/FirebaseAuthUIAuthenticationResult.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.firebase.ui.auth.data.model;
-
-import com.firebase.ui.auth.FirebaseAuthUIActivityResultContract;
-import com.firebase.ui.auth.IdpResponse;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Result of launching a {@link FirebaseAuthUIActivityResultContract}
- */
-public class FirebaseAuthUIAuthenticationResult {
-
- @Nullable
- private final IdpResponse idpResponse;
- @NonNull
- private final Integer resultCode;
-
- public FirebaseAuthUIAuthenticationResult(@NonNull Integer resultCode, @Nullable IdpResponse idpResponse) {
- this.idpResponse = idpResponse;
- this.resultCode = resultCode;
- }
-
- /**
- * The contained {@link IdpResponse} returned from the Firebase library
- */
- @Nullable
- public IdpResponse getIdpResponse() {
- return idpResponse;
- }
-
- /**
- * The result code of the received activity result
- *
- * @see android.app.Activity.RESULT_CANCELED
- * @see android.app.Activity.RESULT_OK
- */
- @NonNull
- public Integer getResultCode() {
- return resultCode;
- }
-
- @Override
- public int hashCode() {
- int result = idpResponse == null ? 0 : idpResponse.hashCode();
- result = 31 * result + resultCode.hashCode();
- return result;
- }
-
- @Override
- public String toString() {
- return "FirebaseAuthUIAuthenticationResult{" +
- "idpResponse=" + idpResponse +
- ", resultCode='" + resultCode +
- '}';
- }
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/data/model/FirebaseAuthUIAuthenticationResult.kt b/auth/src/main/java/com/firebase/ui/auth/data/model/FirebaseAuthUIAuthenticationResult.kt
new file mode 100644
index 000000000..4372307e7
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/data/model/FirebaseAuthUIAuthenticationResult.kt
@@ -0,0 +1,20 @@
+package com.firebase.ui.auth.data.model
+
+import com.firebase.ui.auth.IdpResponse
+
+/**
+ * Result of launching a [FirebaseAuthUIActivityResultContract]
+ */
+data class FirebaseAuthUIAuthenticationResult(
+ /**
+ * The result code of the received activity result
+ *
+ * @see android.app.Activity.RESULT_CANCELED
+ * @see android.app.Activity.RESULT_OK
+ */
+ val resultCode: Int,
+ /**
+ * The contained [IdpResponse] returned from the Firebase library
+ */
+ val idpResponse: IdpResponse?
+)
\ No newline at end of file
diff --git a/auth/src/main/java/com/firebase/ui/auth/data/model/FlowParameters.java b/auth/src/main/java/com/firebase/ui/auth/data/model/FlowParameters.java
deleted file mode 100644
index c6dac950a..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/data/model/FlowParameters.java
+++ /dev/null
@@ -1,223 +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.data.model;
-
-import android.content.Intent;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import com.firebase.ui.auth.AuthMethodPickerLayout;
-import com.firebase.ui.auth.AuthUI;
-import com.firebase.ui.auth.AuthUI.IdpConfig;
-import com.firebase.ui.auth.util.ExtraConstants;
-import com.firebase.ui.auth.util.Preconditions;
-import com.google.firebase.auth.ActionCodeSettings;
-import com.google.firebase.auth.GoogleAuthProvider;
-
-import java.util.Collections;
-import java.util.List;
-
-import androidx.annotation.DrawableRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.StyleRes;
-
-/**
- * Encapsulates the core parameters and data captured during the authentication flow, in a
- * serializable manner, in order to pass data between activities.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class FlowParameters implements Parcelable {
-
- public static final Creator CREATOR = new Creator() {
- @Override
- public FlowParameters createFromParcel(Parcel in) {
- String appName = in.readString();
- List providerInfo = in.createTypedArrayList(IdpConfig.CREATOR);
- IdpConfig defaultProvider = in.readParcelable(IdpConfig.class.getClassLoader());
- int themeId = in.readInt();
- int logoId = in.readInt();
- String termsOfServiceUrl = in.readString();
- String privacyPolicyUrl = in.readString();
- boolean enableCredentials = in.readInt() != 0;
- boolean enableHints = in.readInt() != 0;
- boolean enableAnonymousUpgrade = in.readInt() != 0;
- boolean alwaysShowProviderChoice = in.readInt() != 0;
- boolean lockOrientation = in.readInt() != 0;
- String emailLink = in.readString();
- ActionCodeSettings passwordResetSettings = in.readParcelable(ActionCodeSettings.class.getClassLoader());
- AuthMethodPickerLayout customLayout = in.readParcelable(AuthMethodPickerLayout.class.getClassLoader());
-
- return new FlowParameters(
- appName,
- providerInfo,
- defaultProvider,
- themeId,
- logoId,
- termsOfServiceUrl,
- privacyPolicyUrl,
- enableCredentials,
- enableAnonymousUpgrade,
- alwaysShowProviderChoice,
- lockOrientation,
- emailLink,
- passwordResetSettings,
- customLayout);
- }
-
- @Override
- public FlowParameters[] newArray(int size) {
- return new FlowParameters[size];
- }
- };
-
- @NonNull
- public final String appName;
-
- @NonNull
- public final List providers;
-
- @Nullable
- public final IdpConfig defaultProvider;
-
- @StyleRes
- public final int themeId;
-
- @DrawableRes
- public final int logoId;
-
- @Nullable
- public final String termsOfServiceUrl;
-
- @Nullable
- public final String privacyPolicyUrl;
-
- @Nullable
- public String emailLink;
-
- @Nullable
- public final ActionCodeSettings passwordResetSettings;
-
- public final boolean enableCredentials;
- public final boolean enableAnonymousUpgrade;
- public final boolean alwaysShowProviderChoice;
- public final boolean lockOrientation;
-
- @Nullable
- public final AuthMethodPickerLayout authMethodPickerLayout;
-
- public FlowParameters(
- @NonNull String appName,
- @NonNull List providers,
- @Nullable IdpConfig defaultProvider,
- @StyleRes int themeId,
- @DrawableRes int logoId,
- @Nullable String termsOfServiceUrl,
- @Nullable String privacyPolicyUrl,
- boolean enableCredentials,
- boolean enableAnonymousUpgrade,
- boolean alwaysShowProviderChoice,
- boolean lockOrientation,
- @Nullable String emailLink,
- @Nullable ActionCodeSettings passwordResetSettings,
- @Nullable AuthMethodPickerLayout authMethodPickerLayout) {
- this.appName = Preconditions.checkNotNull(appName, "appName cannot be null");
- this.providers = Collections.unmodifiableList(
- Preconditions.checkNotNull(providers, "providers cannot be null"));
- this.defaultProvider = defaultProvider;
- this.themeId = themeId;
- this.logoId = logoId;
- this.termsOfServiceUrl = termsOfServiceUrl;
- this.privacyPolicyUrl = privacyPolicyUrl;
- this.enableCredentials = enableCredentials;
- this.enableAnonymousUpgrade = enableAnonymousUpgrade;
- this.alwaysShowProviderChoice = alwaysShowProviderChoice;
- this.lockOrientation = lockOrientation;
- this.emailLink = emailLink;
- this.passwordResetSettings = passwordResetSettings;
- this.authMethodPickerLayout = authMethodPickerLayout;
- }
-
- /**
- * Extract FlowParameters from an Intent.
- */
- public static FlowParameters fromIntent(Intent intent) {
- return intent.getParcelableExtra(ExtraConstants.FLOW_PARAMS);
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(appName);
- dest.writeTypedList(providers);
- dest.writeParcelable(defaultProvider, flags);
- dest.writeInt(themeId);
- dest.writeInt(logoId);
- dest.writeString(termsOfServiceUrl);
- dest.writeString(privacyPolicyUrl);
- dest.writeInt(enableCredentials ? 1 : 0);
- dest.writeInt(enableAnonymousUpgrade ? 1 : 0);
- dest.writeInt(alwaysShowProviderChoice ? 1 : 0);
- dest.writeInt(lockOrientation ? 1 : 0);
- dest.writeString(emailLink);
- dest.writeParcelable(passwordResetSettings, flags);
- dest.writeParcelable(authMethodPickerLayout, flags);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public boolean isSingleProviderFlow() {
- return providers.size() == 1;
- }
-
- public boolean isTermsOfServiceUrlProvided() {
- return !TextUtils.isEmpty(termsOfServiceUrl);
- }
-
- public boolean isPrivacyPolicyUrlProvided() {
- return !TextUtils.isEmpty(privacyPolicyUrl);
- }
-
- public boolean isAnonymousUpgradeEnabled() {
- return enableAnonymousUpgrade;
- }
-
- public boolean isPlayServicesRequired() {
- // Play services only required for Google Sign In and the Credentials API
- return isProviderEnabled(GoogleAuthProvider.PROVIDER_ID)
- || enableCredentials;
- }
-
- public boolean isProviderEnabled(@AuthUI.SupportedProvider String provider) {
- for (AuthUI.IdpConfig idp : providers) {
- if (idp.getProviderId().equals(provider)) {
- return true;
- }
- }
-
- return false;
- }
-
- public boolean shouldShowProviderChoice() {
- return defaultProvider == null && (!isSingleProviderFlow() || alwaysShowProviderChoice);
- }
-
- public IdpConfig getDefaultOrFirstProvider() {
- return defaultProvider != null ? defaultProvider : providers.get(0);
- }
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/data/model/FlowParameters.kt b/auth/src/main/java/com/firebase/ui/auth/data/model/FlowParameters.kt
new file mode 100644
index 000000000..8d0a2abd0
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/data/model/FlowParameters.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.data.model
+
+import android.content.Intent
+import android.os.Parcel
+import android.os.Parcelable
+import android.text.TextUtils
+import com.firebase.ui.auth.AuthMethodPickerLayout
+import com.firebase.ui.auth.AuthUI
+import com.firebase.ui.auth.AuthUI.IdpConfig
+import com.firebase.ui.auth.util.ExtraConstants
+import com.firebase.ui.auth.util.Preconditions
+import com.google.firebase.auth.ActionCodeSettings
+import com.google.firebase.auth.GoogleAuthProvider
+import java.util.Collections
+import androidx.annotation.DrawableRes
+import androidx.annotation.NonNull
+import androidx.annotation.Nullable
+import androidx.annotation.RestrictTo
+import androidx.annotation.StyleRes
+
+/**
+ * Encapsulates the core parameters and data captured during the authentication flow, in a
+ * serializable manner, in order to pass data between activities.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class FlowParameters(
+ @JvmField val appName: String,
+ providers: List,
+ @JvmField val defaultProvider: IdpConfig?,
+ @StyleRes @JvmField val themeId: Int,
+ @DrawableRes @JvmField val logoId: Int,
+ @JvmField val termsOfServiceUrl: String?,
+ @JvmField val privacyPolicyUrl: String?,
+ @JvmField val enableCredentials: Boolean,
+ @JvmField val enableAnonymousUpgrade: Boolean,
+ @JvmField val alwaysShowProviderChoice: Boolean,
+ @JvmField val lockOrientation: Boolean,
+ @JvmField var emailLink: String?,
+ @JvmField val passwordResetSettings: ActionCodeSettings?,
+ @JvmField val authMethodPickerLayout: AuthMethodPickerLayout?
+) : Parcelable {
+
+ // Wrap the providers list in an unmodifiable list to mimic the original behavior.
+ @JvmField
+ val providers: List =
+ Collections.unmodifiableList(Preconditions.checkNotNull(providers, "providers cannot be null"))
+
+ init {
+ Preconditions.checkNotNull(appName, "appName cannot be null")
+ }
+
+ /**
+ * Constructor used for parcelable.
+ */
+ private constructor(parcel: Parcel) : this(
+ appName = Preconditions.checkNotNull(parcel.readString(), "appName cannot be null"),
+ providers = parcel.createTypedArrayList(IdpConfig.CREATOR)
+ ?: emptyList(),
+ defaultProvider = parcel.readParcelable(IdpConfig::class.java.classLoader),
+ themeId = parcel.readInt(),
+ logoId = parcel.readInt(),
+ termsOfServiceUrl = parcel.readString(),
+ privacyPolicyUrl = parcel.readString(),
+ enableCredentials = parcel.readInt() != 0,
+ enableAnonymousUpgrade = parcel.readInt() != 0,
+ alwaysShowProviderChoice = parcel.readInt() != 0,
+ lockOrientation = parcel.readInt() != 0,
+ emailLink = parcel.readString(),
+ passwordResetSettings = parcel.readParcelable(ActionCodeSettings::class.java.classLoader),
+ authMethodPickerLayout = parcel.readParcelable(AuthMethodPickerLayout::class.java.classLoader)
+ )
+
+ /**
+ * Extract FlowParameters from an Intent.
+ */
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): FlowParameters {
+ return FlowParameters(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+
+ @JvmStatic
+ fun fromIntent(intent: Intent): FlowParameters =
+ // getParcelableExtra returns a nullable type so we use !! to mirror the Java behavior.
+ intent.getParcelableExtra(ExtraConstants.FLOW_PARAMS)!!
+ }
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(appName)
+ dest.writeTypedList(providers)
+ dest.writeParcelable(defaultProvider, flags)
+ dest.writeInt(themeId)
+ dest.writeInt(logoId)
+ dest.writeString(termsOfServiceUrl)
+ dest.writeString(privacyPolicyUrl)
+ dest.writeInt(if (enableCredentials) 1 else 0)
+ dest.writeInt(if (enableAnonymousUpgrade) 1 else 0)
+ dest.writeInt(if (alwaysShowProviderChoice) 1 else 0)
+ dest.writeInt(if (lockOrientation) 1 else 0)
+ dest.writeString(emailLink)
+ dest.writeParcelable(passwordResetSettings, flags)
+ dest.writeParcelable(authMethodPickerLayout, flags)
+ }
+
+ override fun describeContents(): Int = 0
+
+ fun isSingleProviderFlow(): Boolean = providers.size == 1
+
+ fun isTermsOfServiceUrlProvided(): Boolean = !TextUtils.isEmpty(termsOfServiceUrl)
+
+ fun isPrivacyPolicyUrlProvided(): Boolean = !TextUtils.isEmpty(privacyPolicyUrl)
+
+ fun isAnonymousUpgradeEnabled(): Boolean = enableAnonymousUpgrade
+
+ fun isPlayServicesRequired(): Boolean {
+ // Play services only required for Google Sign In and the Credentials API
+ return isProviderEnabled(GoogleAuthProvider.PROVIDER_ID) || enableCredentials
+ }
+
+ fun isProviderEnabled(@AuthUI.SupportedProvider provider: String): Boolean {
+ for (idp in providers) {
+ if (idp.providerId == provider) {
+ return true
+ }
+ }
+ return false
+ }
+
+ fun shouldShowProviderChoice(): Boolean {
+ return defaultProvider == null && (!isSingleProviderFlow() || alwaysShowProviderChoice)
+ }
+
+ fun getDefaultOrFirstProvider(): IdpConfig {
+ return defaultProvider ?: providers[0]
+ }
+}
\ No newline at end of file
diff --git a/auth/src/main/java/com/firebase/ui/auth/data/remote/SignInKickstarter.kt b/auth/src/main/java/com/firebase/ui/auth/data/remote/SignInKickstarter.kt
index b665ce1d4..7f9f3ae79 100644
--- a/auth/src/main/java/com/firebase/ui/auth/data/remote/SignInKickstarter.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/data/remote/SignInKickstarter.kt
@@ -75,7 +75,7 @@ class SignInKickstarter(application: Application?) : SignInViewModelBase(applica
*/
private fun startAuthMethodChoice() {
if (!arguments.shouldShowProviderChoice()) {
- val firstIdpConfig = arguments.defaultOrFirstProvider
+ val firstIdpConfig = arguments.getDefaultOrFirstProvider()
val firstProvider = firstIdpConfig.providerId
when (firstProvider) {
AuthUI.EMAIL_LINK_PROVIDER, EmailAuthProvider.PROVIDER_ID ->
@@ -91,7 +91,7 @@ class SignInKickstarter(application: Application?) : SignInViewModelBase(applica
setResult(
Resource.forFailure(
IntentRequiredException(
- PhoneActivity.createIntent(app, arguments, firstIdpConfig.params),
+ PhoneActivity.createIntent(app, arguments, firstIdpConfig.getParams()),
RequestCodes.PHONE_FLOW
)
)
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailFragment.java b/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailFragment.java
deleted file mode 100644
index e423a30c7..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailFragment.java
+++ /dev/null
@@ -1,233 +0,0 @@
-package com.firebase.ui.auth.ui.email;
-
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import com.firebase.ui.auth.AuthUI;
-import com.firebase.ui.auth.R;
-import com.firebase.ui.auth.data.model.FlowParameters;
-import com.firebase.ui.auth.data.model.User;
-import com.firebase.ui.auth.ui.FragmentBase;
-import com.firebase.ui.auth.util.ExtraConstants;
-import com.firebase.ui.auth.util.data.PrivacyDisclosureUtils;
-import com.firebase.ui.auth.util.ui.ImeHelper;
-import com.firebase.ui.auth.util.ui.fieldvalidators.EmailFieldValidator;
-import com.google.android.material.snackbar.Snackbar;
-import com.google.android.material.textfield.TextInputLayout;
-import com.google.firebase.auth.EmailAuthProvider;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.fragment.app.FragmentActivity;
-import androidx.lifecycle.ViewModelProvider;
-
-import static com.firebase.ui.auth.AuthUI.EMAIL_LINK_PROVIDER;
-
-/**
- * Fragment that shows a form with an email field and checks for existing accounts with that email.
- *
- * Host Activities should implement {@link CheckEmailFragment.CheckEmailListener}.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class CheckEmailFragment extends FragmentBase implements
- View.OnClickListener,
- ImeHelper.DonePressedListener {
-
- public static final String TAG = "CheckEmailFragment";
- private CheckEmailHandler mHandler;
- private Button mSignInButton;
- private Button mSignUpButton;
- private ProgressBar mProgressBar;
- private EditText mEmailEditText;
- private TextInputLayout mEmailLayout;
- private EmailFieldValidator mEmailFieldValidator;
- private CheckEmailListener mListener;
-
- public static CheckEmailFragment newInstance(@Nullable String email) {
- CheckEmailFragment fragment = new CheckEmailFragment();
- Bundle args = new Bundle();
- args.putString(ExtraConstants.EMAIL, email);
- fragment.setArguments(args);
- return fragment;
- }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater,
- @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- return inflater.inflate(R.layout.fui_check_email_layout, container, false);
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- mSignInButton = view.findViewById(R.id.button_sign_in);
- mSignUpButton = view.findViewById(R.id.button_sign_up);
- mProgressBar = view.findViewById(R.id.top_progress_bar);
-
- mEmailLayout = view.findViewById(R.id.email_layout);
- mEmailEditText = view.findViewById(R.id.email);
- mEmailFieldValidator = new EmailFieldValidator(mEmailLayout);
- mEmailLayout.setOnClickListener(this);
- mEmailEditText.setOnClickListener(this);
-
- TextView headerText = view.findViewById(R.id.header_text);
- if (headerText != null) {
- headerText.setVisibility(View.GONE);
- }
-
- ImeHelper.setImeOnDoneListener(mEmailEditText, this);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- mEmailEditText.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
- }
-
- // Set listeners for our new sign‑in and sign‑up buttons.
- mSignInButton.setOnClickListener(this);
- mSignUpButton.setOnClickListener(this);
-
- TextView termsText = view.findViewById(R.id.email_tos_and_pp_text);
- TextView footerText = view.findViewById(R.id.email_footer_tos_and_pp_text);
- FlowParameters flowParameters = getFlowParams();
-
- if (!flowParameters.shouldShowProviderChoice()) {
- PrivacyDisclosureUtils.setupTermsOfServiceAndPrivacyPolicyText(requireContext(),
- flowParameters,
- termsText);
- } else {
- termsText.setVisibility(View.GONE);
- PrivacyDisclosureUtils.setupTermsOfServiceFooter(requireContext(),
- flowParameters,
- footerText);
- }
- }
-
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- mHandler = new ViewModelProvider(this).get(CheckEmailHandler.class);
- mHandler.init(getFlowParams());
-
- FragmentActivity activity = getActivity();
- if (!(activity instanceof CheckEmailListener)) {
- throw new IllegalStateException("Activity must implement CheckEmailListener");
- }
- mListener = (CheckEmailListener) activity;
-
- // Removed the observer on mHandler.getOperation() since we no longer rely on provider info.
-
- if (savedInstanceState == null) {
- String email = getArguments().getString(ExtraConstants.EMAIL);
- if (!TextUtils.isEmpty(email)) {
- mEmailEditText.setText(email);
- // Previously auto-triggering the check is now removed.
- } else if (getFlowParams().enableCredentials) {
- mHandler.fetchCredential();
- }
- }
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- mHandler.onActivityResult(requestCode, resultCode, data);
- }
-
- @Override
- public void onClick(View view) {
- int id = view.getId();
-
- if (id == R.id.button_sign_in) {
- signIn();
- } else if (id == R.id.button_sign_up) {
- signUp();
- } else if (id == R.id.email_layout || id == R.id.email) {
- mEmailLayout.setError(null);
- }
- }
-
- @Override
- public void onDonePressed() {
- // When the user hits “done” on the keyboard, default to sign‑in.
- signIn();
- }
-
- private String getEmailProvider() {
- // Iterate through all IdpConfig entries
- for (AuthUI.IdpConfig config : getFlowParams().providers) {
- // Assuming there is a getter for the provider ID
- if (EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD.equals(config.getProviderId())) {
- return EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD;
- }
- }
- // Default to standard email/password
- return EmailAuthProvider.PROVIDER_ID;
- }
-
- private void signIn() {
- String email = mEmailEditText.getText().toString();
- if (mEmailFieldValidator.validate(email)) {
- String provider = getEmailProvider();
- User user = new User.Builder(provider, email).build();
- mListener.onExistingEmailUser(user);
- }
- }
-
- private void signUp() {
- String email = mEmailEditText.getText().toString();
- if (mEmailFieldValidator.validate(email)) {
- String provider = getEmailProvider();
- User user = new User.Builder(provider, email).build();
- mListener.onNewUser(user);
- }
- }
-
- @Override
- public void showProgress(int message) {
- // Disable both buttons while progress is showing.
- mSignInButton.setEnabled(false);
- mSignUpButton.setEnabled(false);
- mProgressBar.setVisibility(View.VISIBLE);
- }
-
- @Override
- public void hideProgress() {
- mSignInButton.setEnabled(true);
- mSignUpButton.setEnabled(true);
- mProgressBar.setVisibility(View.INVISIBLE);
- }
-
- /**
- * Interface to be implemented by Activities hosting this Fragment.
- */
- interface CheckEmailListener {
-
- /**
- * Email entered belongs to an existing email user (sign‑in flow).
- */
- void onExistingEmailUser(User user);
-
- /**
- * Email entered belongs to an existing IDP user.
- */
- void onExistingIdpUser(User user);
-
- /**
- * Email entered does not belong to an existing user (sign‑up flow).
- */
- void onNewUser(User user);
-
- /**
- * Email entered corresponds to an existing user whose sign in methods we do not support.
- */
- void onDeveloperFailure(Exception e);
- }
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailFragment.kt b/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailFragment.kt
new file mode 100644
index 000000000..718268e35
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailFragment.kt
@@ -0,0 +1,188 @@
+package com.firebase.ui.auth.ui.email
+
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.EditText
+import android.widget.ProgressBar
+import android.widget.TextView
+import com.firebase.ui.auth.R
+import com.firebase.ui.auth.data.model.FlowParameters
+import com.firebase.ui.auth.data.model.User
+import com.firebase.ui.auth.ui.FragmentBase
+import com.firebase.ui.auth.util.ExtraConstants
+import com.firebase.ui.auth.util.data.PrivacyDisclosureUtils
+import com.firebase.ui.auth.util.ui.ImeHelper
+import com.firebase.ui.auth.util.ui.fieldvalidators.EmailFieldValidator
+import com.google.android.material.textfield.TextInputLayout
+import com.google.firebase.auth.EmailAuthProvider
+import androidx.annotation.RestrictTo
+import androidx.lifecycle.ViewModelProvider
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class CheckEmailFragment : FragmentBase(), View.OnClickListener, ImeHelper.DonePressedListener {
+
+ private lateinit var mHandler: CheckEmailHandler
+ private lateinit var mListener: CheckEmailListener
+ private lateinit var mEmailEditText: EditText
+ private lateinit var mEmailLayout: TextInputLayout
+ private lateinit var mSignInButton: Button
+ private lateinit var mSignUpButton: Button
+ private lateinit var mProgressBar: ProgressBar
+ private lateinit var mEmailFieldValidator: EmailFieldValidator
+
+ companion object {
+ const val TAG = "CheckEmailFragment"
+
+ @JvmStatic
+ fun newInstance(email: String?): CheckEmailFragment {
+ return CheckEmailFragment().apply {
+ arguments = Bundle().apply {
+ putString(ExtraConstants.EMAIL, email)
+ }
+ }
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.fui_check_email_layout, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ mSignInButton = view.findViewById(R.id.button_sign_in)
+ mSignUpButton = view.findViewById(R.id.button_sign_up)
+ mProgressBar = view.findViewById(R.id.top_progress_bar)
+
+ mEmailLayout = view.findViewById(R.id.email_layout)
+ mEmailEditText = view.findViewById(R.id.email)
+ mEmailFieldValidator = EmailFieldValidator(mEmailLayout)
+ mEmailLayout.setOnClickListener(this)
+ mEmailEditText.setOnClickListener(this)
+
+ val headerText: TextView? = view.findViewById(R.id.header_text)
+ headerText?.visibility = View.GONE
+
+ ImeHelper.setImeOnDoneListener(mEmailEditText, this)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ mEmailEditText.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO
+ }
+
+ mSignInButton.setOnClickListener(this)
+ mSignUpButton.setOnClickListener(this)
+
+ val termsText: TextView? = view.findViewById(R.id.email_tos_and_pp_text)
+ val footerText: TextView? = view.findViewById(R.id.email_footer_tos_and_pp_text)
+ val flowParameters: FlowParameters = getFlowParams()
+
+ if (!flowParameters.shouldShowProviderChoice()) {
+ PrivacyDisclosureUtils.setupTermsOfServiceAndPrivacyPolicyText(
+ requireContext(),
+ flowParameters,
+ termsText
+ )
+ } else {
+ termsText?.visibility = View.GONE
+ PrivacyDisclosureUtils.setupTermsOfServiceFooter(
+ requireContext(),
+ flowParameters,
+ footerText
+ )
+ }
+ }
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ mHandler = ViewModelProvider(this).get(CheckEmailHandler::class.java)
+ mHandler.init(getFlowParams())
+
+ val activity = activity
+ if (activity !is CheckEmailListener) {
+ throw IllegalStateException("Activity must implement CheckEmailListener")
+ }
+ mListener = activity
+
+ if (savedInstanceState == null) {
+ val email = arguments?.getString(ExtraConstants.EMAIL)
+ if (!TextUtils.isEmpty(email)) {
+ mEmailEditText.setText(email)
+ } else if (getFlowParams().enableCredentials) {
+ mHandler.fetchCredential()
+ }
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ mHandler.onActivityResult(requestCode, resultCode, data)
+ }
+
+ override fun onClick(view: View) {
+ when (view.id) {
+ R.id.button_sign_in -> signIn()
+ R.id.button_sign_up -> signUp()
+ R.id.email_layout, R.id.email -> mEmailLayout.error = null
+ }
+ }
+
+ override fun onDonePressed() {
+ // When the user hits "done" on the keyboard, default to sign‑in.
+ signIn()
+ }
+
+ private fun getEmailProvider(): String {
+ for (config in getFlowParams().providers) {
+ if (EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD == config.providerId) {
+ return EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD
+ }
+ }
+ return EmailAuthProvider.PROVIDER_ID
+ }
+
+ private fun signIn() {
+ val email = mEmailEditText.text.toString()
+ if (mEmailFieldValidator.validate(email)) {
+ val provider = getEmailProvider()
+ val user = User.Builder(provider, email).build()
+ mListener.onExistingEmailUser(user)
+ }
+ }
+
+ private fun signUp() {
+ val email = mEmailEditText.text.toString()
+ if (mEmailFieldValidator.validate(email)) {
+ val provider = getEmailProvider()
+ val user = User.Builder(provider, email).build()
+ mListener.onNewUser(user)
+ }
+ }
+
+ override fun showProgress(message: Int) {
+ mSignInButton.isEnabled = false
+ mSignUpButton.isEnabled = false
+ mProgressBar.visibility = View.VISIBLE
+ }
+
+ override fun hideProgress() {
+ mSignInButton.isEnabled = true
+ mSignUpButton.isEnabled = true
+ mProgressBar.visibility = View.INVISIBLE
+ }
+
+ interface CheckEmailListener {
+ fun onExistingEmailUser(user: User)
+ fun onExistingIdpUser(user: User)
+ fun onNewUser(user: User)
+ fun onDeveloperFailure(e: Exception)
+ }
+}
\ No newline at end of file
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailHandler.java b/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailHandler.java
deleted file mode 100644
index 3a911a417..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailHandler.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package com.firebase.ui.auth.ui.email;
-
-import android.app.Activity;
-import android.app.Application;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.util.Log;
-
-import com.firebase.ui.auth.data.model.PendingIntentRequiredException;
-import com.firebase.ui.auth.data.model.Resource;
-import com.firebase.ui.auth.data.model.User;
-import com.firebase.ui.auth.util.data.ProviderUtils;
-import com.firebase.ui.auth.viewmodel.AuthViewModelBase;
-import com.firebase.ui.auth.viewmodel.RequestCodes;
-import com.google.android.gms.common.api.ApiException;
-import com.google.android.gms.tasks.Task;
-
-// New Identity API imports:
-import com.google.android.gms.auth.api.identity.BeginSignInRequest;
-import com.google.android.gms.auth.api.identity.SignInClient;
-import com.google.android.gms.auth.api.identity.SignInCredential;
-import com.google.android.gms.auth.api.identity.Identity;
-
-import androidx.annotation.Nullable;
-
-public class CheckEmailHandler extends AuthViewModelBase {
- private static final String TAG = "CheckEmailHandler";
-
- public CheckEmailHandler(Application application) {
- super(application);
- }
-
- /**
- * Initiates a hint picker flow using the new Identity API.
- * This replaces the deprecated Credentials API call.
- */
- public void fetchCredential() {
- // Build a sign-in request that supports password-based sign in,
- // which will trigger the hint picker UI for email addresses.
- SignInClient signInClient = Identity.getSignInClient(getApplication());
- BeginSignInRequest signInRequest = BeginSignInRequest.builder()
- .setPasswordRequestOptions(
- BeginSignInRequest.PasswordRequestOptions.builder()
- .setSupported(true)
- .build())
- .build();
-
- signInClient.beginSignIn(signInRequest)
- .addOnSuccessListener(result -> {
- // The new API returns a PendingIntent to launch the hint picker.
- PendingIntent pendingIntent = result.getPendingIntent();
- setResult(Resource.forFailure(
- new PendingIntentRequiredException(pendingIntent, RequestCodes.CRED_HINT)));
- })
- .addOnFailureListener(e -> {
- Log.e(TAG, "beginSignIn failed", e);
- setResult(Resource.forFailure(e));
- });
- }
-
- /**
- * Fetches the top provider for the given email.
- */
- public void fetchProvider(final String email) {
- setResult(Resource.forLoading());
- ProviderUtils.fetchTopProvider(getAuth(), getArguments(), email)
- .addOnCompleteListener(task -> {
- if (task.isSuccessful()) {
- setResult(Resource.forSuccess(
- new User.Builder(task.getResult(), email).build()));
- } else {
- setResult(Resource.forFailure(task.getException()));
- }
- });
- }
-
- /**
- * Handles the result from the hint picker launched via the new Identity API.
- */
- public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
- if (requestCode != RequestCodes.CRED_HINT || resultCode != Activity.RESULT_OK) {
- return;
- }
-
- setResult(Resource.forLoading());
- SignInClient signInClient = Identity.getSignInClient(getApplication());
- try {
- // Retrieve the SignInCredential from the returned intent.
- SignInCredential credential = signInClient.getSignInCredentialFromIntent(data);
- final String email = credential.getId();
-
- ProviderUtils.fetchTopProvider(getAuth(), getArguments(), email)
- .addOnCompleteListener(task -> {
- if (task.isSuccessful()) {
- setResult(Resource.forSuccess(new User.Builder(task.getResult(), email)
- .setName(credential.getDisplayName())
- .setPhotoUri(credential.getProfilePictureUri())
- .build()));
- } else {
- setResult(Resource.forFailure(task.getException()));
- }
- });
- } catch (ApiException e) {
- Log.e(TAG, "getSignInCredentialFromIntent failed", e);
- setResult(Resource.forFailure(e));
- }
- }
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailHandler.kt b/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailHandler.kt
new file mode 100644
index 000000000..9ba6da0f0
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailHandler.kt
@@ -0,0 +1,106 @@
+package com.firebase.ui.auth.ui.email
+
+import android.app.Activity
+import android.app.Application
+import android.app.PendingIntent
+import android.content.Intent
+import android.util.Log
+import com.firebase.ui.auth.data.model.PendingIntentRequiredException
+import com.firebase.ui.auth.data.model.Resource
+import com.firebase.ui.auth.data.model.User
+import com.firebase.ui.auth.util.data.ProviderUtils
+import com.firebase.ui.auth.viewmodel.AuthViewModelBase
+import com.firebase.ui.auth.viewmodel.RequestCodes
+import com.google.android.gms.auth.api.identity.BeginSignInRequest
+import com.google.android.gms.auth.api.identity.Identity
+import com.google.android.gms.auth.api.identity.SignInCredential
+import com.google.android.gms.auth.api.identity.SignInClient
+import com.google.android.gms.common.api.ApiException
+import androidx.annotation.Nullable
+
+class CheckEmailHandler(application: Application) : AuthViewModelBase(application) {
+ companion object {
+ private const val TAG = "CheckEmailHandler"
+ }
+
+ /**
+ * Initiates a hint picker flow using the new Identity API.
+ * This replaces the deprecated Credentials API call.
+ */
+ fun fetchCredential() {
+ val signInClient: SignInClient = Identity.getSignInClient(getApplication())
+ val signInRequest = BeginSignInRequest.builder()
+ .setPasswordRequestOptions(
+ BeginSignInRequest.PasswordRequestOptions.builder()
+ .setSupported(true)
+ .build()
+ )
+ .build()
+
+ signInClient.beginSignIn(signInRequest)
+ .addOnSuccessListener { result ->
+ // The new API returns a PendingIntent to launch the hint picker.
+ val pendingIntent: PendingIntent = result.pendingIntent
+ setResult(
+ Resource.forFailure(
+ PendingIntentRequiredException(pendingIntent, RequestCodes.CRED_HINT)
+ )
+ )
+ }
+ .addOnFailureListener { e ->
+ Log.e(TAG, "beginSignIn failed", e)
+ setResult(Resource.forFailure(e))
+ }
+ }
+
+ /**
+ * Fetches the top provider for the given email.
+ */
+ fun fetchProvider(email: String) {
+ setResult(Resource.forLoading())
+ ProviderUtils.fetchTopProvider(getAuth(), getArguments(), email)
+ .addOnCompleteListener { task ->
+ if (task.isSuccessful) {
+ setResult(Resource.forSuccess(User.Builder(task.result, email).build()))
+ } else {
+ setResult(Resource.forFailure(task.exception ?: Exception("Unknown error")))
+ }
+ }
+ }
+
+ /**
+ * Handles the result from the hint picker launched via the new Identity API.
+ */
+ fun onActivityResult(requestCode: Int, resultCode: Int, @Nullable data: Intent?) {
+ if (requestCode != RequestCodes.CRED_HINT || resultCode != Activity.RESULT_OK) {
+ return
+ }
+
+ setResult(Resource.forLoading())
+ val signInClient: SignInClient = Identity.getSignInClient(getApplication())
+ try {
+ // Retrieve the SignInCredential from the returned intent.
+ val credential: SignInCredential = signInClient.getSignInCredentialFromIntent(data)
+ val email: String = credential.id
+
+ ProviderUtils.fetchTopProvider(getAuth(), getArguments(), email)
+ .addOnCompleteListener { task ->
+ if (task.isSuccessful) {
+ setResult(
+ Resource.forSuccess(
+ User.Builder(task.result, email)
+ .setName(credential.displayName)
+ .setPhotoUri(credential.profilePictureUri)
+ .build()
+ )
+ )
+ } else {
+ setResult(Resource.forFailure(task.exception ?: Exception("Unknown error")))
+ }
+ }
+ } catch (e: ApiException) {
+ Log.e(TAG, "getSignInCredentialFromIntent failed", e)
+ setResult(Resource.forFailure(e))
+ }
+ }
+}
\ No newline at end of file
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/email/EmailActivity.java b/auth/src/main/java/com/firebase/ui/auth/ui/email/EmailActivity.java
deleted file mode 100644
index feabf506d..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/ui/email/EmailActivity.java
+++ /dev/null
@@ -1,247 +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.ui.email;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-import com.firebase.ui.auth.AuthUI;
-import com.firebase.ui.auth.ErrorCodes;
-import com.firebase.ui.auth.FirebaseUiException;
-import com.firebase.ui.auth.IdpResponse;
-import com.firebase.ui.auth.R;
-import com.firebase.ui.auth.data.model.FlowParameters;
-import com.firebase.ui.auth.data.model.User;
-import com.firebase.ui.auth.ui.AppCompatBase;
-import com.firebase.ui.auth.ui.idp.WelcomeBackIdpPrompt;
-import com.firebase.ui.auth.util.ExtraConstants;
-import com.firebase.ui.auth.util.data.EmailLinkPersistenceManager;
-import com.firebase.ui.auth.util.data.ProviderUtils;
-import com.firebase.ui.auth.viewmodel.RequestCodes;
-import com.google.android.material.textfield.TextInputLayout;
-import com.google.firebase.auth.ActionCodeSettings;
-import com.google.firebase.auth.EmailAuthProvider;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.StringRes;
-import androidx.core.view.ViewCompat;
-import androidx.fragment.app.FragmentTransaction;
-
-import static com.firebase.ui.auth.AuthUI.EMAIL_LINK_PROVIDER;
-
-/**
- * Activity to control the entire email sign up flow. Plays host to {@link CheckEmailFragment} and
- * {@link RegisterEmailFragment} and triggers {@link WelcomeBackPasswordPrompt} and {@link
- * WelcomeBackIdpPrompt}.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class EmailActivity extends AppCompatBase implements CheckEmailFragment.CheckEmailListener,
- RegisterEmailFragment.AnonymousUpgradeListener, EmailLinkFragment
- .TroubleSigningInListener, TroubleSigningInFragment.ResendEmailListener {
-
- public static Intent createIntent(Context context, FlowParameters flowParams) {
- return createBaseIntent(context, EmailActivity.class, flowParams);
- }
-
- public static Intent createIntent(Context context, FlowParameters flowParams, String email) {
- return createBaseIntent(context, EmailActivity.class, flowParams)
- .putExtra(ExtraConstants.EMAIL, email);
- }
-
- public static Intent createIntentForLinking(Context context, FlowParameters flowParams,
- IdpResponse responseForLinking) {
- return createIntent(context, flowParams, responseForLinking.getEmail())
- .putExtra(ExtraConstants.IDP_RESPONSE, responseForLinking);
- }
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.fui_activity_register_email);
-
- if (savedInstanceState != null) {
- return;
- }
-
- // Get email from intent (can be null)
- String email = getIntent().getExtras().getString(ExtraConstants.EMAIL);
-
- IdpResponse responseForLinking = getIntent().getExtras().getParcelable(ExtraConstants
- .IDP_RESPONSE);
- if (email != null && responseForLinking != null) {
- // got here from WelcomeBackEmailLinkPrompt
- AuthUI.IdpConfig emailConfig = ProviderUtils.getConfigFromIdpsOrThrow(
- getFlowParams().providers, EMAIL_LINK_PROVIDER);
- ActionCodeSettings actionCodeSettings = emailConfig.getParams().getParcelable
- (ExtraConstants.ACTION_CODE_SETTINGS);
-
- EmailLinkPersistenceManager.getInstance().saveIdpResponseForLinking(getApplication(),
- responseForLinking);
-
- boolean forceSameDevice =
- emailConfig.getParams().getBoolean(ExtraConstants.FORCE_SAME_DEVICE);
- EmailLinkFragment fragment = EmailLinkFragment.newInstance(email, actionCodeSettings,
- responseForLinking, forceSameDevice);
- switchFragment(fragment, R.id.fragment_register_email, EmailLinkFragment.TAG);
- } else {
- AuthUI.IdpConfig emailConfig = ProviderUtils.getConfigFromIdps(
- getFlowParams().providers, EmailAuthProvider.PROVIDER_ID);
-
- if (emailConfig != null) {
- email = emailConfig.getParams().getString(ExtraConstants.DEFAULT_EMAIL);;
- }
- // Start with check email
- CheckEmailFragment fragment = CheckEmailFragment.newInstance(email);
- switchFragment(fragment, R.id.fragment_register_email, CheckEmailFragment.TAG);
- }
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == RequestCodes.WELCOME_BACK_EMAIL_FLOW
- || requestCode == RequestCodes.WELCOME_BACK_IDP_FLOW) {
- finish(resultCode, data);
- }
- }
-
- @Override
- public void onExistingEmailUser(User user) {
- if (user.getProviderId().equals(EMAIL_LINK_PROVIDER)) {
- AuthUI.IdpConfig emailConfig = ProviderUtils.getConfigFromIdpsOrThrow(
- getFlowParams().providers, EMAIL_LINK_PROVIDER);
- showRegisterEmailLinkFragment(
- emailConfig, user.getEmail());
- } else {
- startActivityForResult(
- WelcomeBackPasswordPrompt.createIntent(
- this, getFlowParams(), new IdpResponse.Builder(user).build()),
- RequestCodes.WELCOME_BACK_EMAIL_FLOW);
- setSlideAnimation();
- }
- }
-
- @Override
- public void onExistingIdpUser(User user) {
- // Existing social user, direct them to sign in using their chosen provider.
- startActivityForResult(
- WelcomeBackIdpPrompt.createIntent(this, getFlowParams(), user),
- RequestCodes.WELCOME_BACK_IDP_FLOW);
- setSlideAnimation();
- }
-
- @Override
- public void onNewUser(User user) {
- // New user, direct them to create an account with email/password
- // if account creation is enabled in SignInIntentBuilder
-
- TextInputLayout emailLayout = findViewById(R.id.email_layout);
- AuthUI.IdpConfig emailConfig = ProviderUtils.getConfigFromIdps(getFlowParams().providers,
- EmailAuthProvider.PROVIDER_ID);
-
- if (emailConfig == null) {
- emailConfig = ProviderUtils.getConfigFromIdps(getFlowParams().providers,
- EMAIL_LINK_PROVIDER);
- }
-
- if (emailConfig.getParams().getBoolean(ExtraConstants.ALLOW_NEW_EMAILS, true)) {
- FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
- if (emailConfig.getProviderId().equals(EMAIL_LINK_PROVIDER)) {
- showRegisterEmailLinkFragment(emailConfig, user.getEmail());
- } else {
- RegisterEmailFragment fragment = RegisterEmailFragment.newInstance(user);
- ft.replace(R.id.fragment_register_email, fragment, RegisterEmailFragment.TAG);
- if (emailLayout != null) {
- String emailFieldName = getString(R.string.fui_email_field_name);
- ViewCompat.setTransitionName(emailLayout, emailFieldName);
- ft.addSharedElement(emailLayout, emailFieldName);
- }
- ft.disallowAddToBackStack().commit();
- }
- } else {
- emailLayout.setError(getString(R.string.fui_error_email_does_not_exist));
- }
- }
-
- @Override
- public void onTroubleSigningIn(String email) {
- TroubleSigningInFragment troubleSigningInFragment = TroubleSigningInFragment.newInstance
- (email);
- switchFragment(troubleSigningInFragment, R.id.fragment_register_email,
- TroubleSigningInFragment.TAG, true, true);
- }
-
- @Override
- public void onClickResendEmail(String email) {
- if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
- // We're assuming that to get to the TroubleSigningInFragment, we went through
- // the EmailLinkFragment, which was added to the fragment back stack.
- // From here, we're going to register the EmailLinkFragment again, meaning we'd have to
- // pop off the back stack twice to return to the nascar screen. To avoid this,
- // we pre-emptively pop off the last EmailLinkFragment here.
- getSupportFragmentManager().popBackStack();
- }
- AuthUI.IdpConfig emailConfig = ProviderUtils.getConfigFromIdpsOrThrow(
- getFlowParams().providers, EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD);
- showRegisterEmailLinkFragment(
- emailConfig, email);
- }
-
- @Override
- public void onSendEmailFailure(Exception e) {
- finishOnDeveloperError(e);
- }
-
- @Override
- public void onDeveloperFailure(Exception e) {
- finishOnDeveloperError(e);
- }
-
- private void finishOnDeveloperError(Exception e) {
- finish(RESULT_CANCELED, IdpResponse.getErrorIntent(new FirebaseUiException(
- ErrorCodes.DEVELOPER_ERROR, e.getMessage())));
- }
-
- private void setSlideAnimation() {
- // Make the next activity slide in
- overridePendingTransition(R.anim.fui_slide_in_right, R.anim.fui_slide_out_left);
- }
-
- private void showRegisterEmailLinkFragment(AuthUI.IdpConfig emailConfig,
- String email) {
- ActionCodeSettings actionCodeSettings = emailConfig.getParams().getParcelable
- (ExtraConstants.ACTION_CODE_SETTINGS);
- EmailLinkFragment fragment = EmailLinkFragment.newInstance(email,
- actionCodeSettings);
- switchFragment(fragment, R.id.fragment_register_email, EmailLinkFragment.TAG);
- }
-
- @Override
- public void showProgress(@StringRes int message) {
- throw new UnsupportedOperationException("Email fragments must handle progress updates.");
- }
-
- @Override
- public void hideProgress() {
- throw new UnsupportedOperationException("Email fragments must handle progress updates.");
- }
-
- @Override
- public void onMergeFailure(IdpResponse response) {
- finish(ErrorCodes.ANONYMOUS_UPGRADE_MERGE_CONFLICT, response.toIntent());
- }
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/email/EmailActivity.kt b/auth/src/main/java/com/firebase/ui/auth/ui/email/EmailActivity.kt
new file mode 100644
index 000000000..08b4d979c
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/email/EmailActivity.kt
@@ -0,0 +1,296 @@
+/*
+ * 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.ui.email
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.annotation.Nullable
+import androidx.annotation.RestrictTo
+import androidx.annotation.StringRes
+import androidx.core.view.ViewCompat
+import androidx.fragment.app.FragmentTransaction
+import com.firebase.ui.auth.AuthUI
+import com.firebase.ui.auth.ErrorCodes
+import com.firebase.ui.auth.FirebaseUiException
+import com.firebase.ui.auth.IdpResponse
+import com.firebase.ui.auth.R
+import com.firebase.ui.auth.data.model.FlowParameters
+import com.firebase.ui.auth.data.model.User
+import com.firebase.ui.auth.ui.AppCompatBase
+import com.firebase.ui.auth.ui.idp.WelcomeBackIdpPrompt
+import com.firebase.ui.auth.util.ExtraConstants
+import com.firebase.ui.auth.util.data.EmailLinkPersistenceManager
+import com.firebase.ui.auth.util.data.ProviderUtils
+import com.firebase.ui.auth.viewmodel.RequestCodes
+import com.google.android.material.textfield.TextInputLayout
+import com.google.firebase.auth.ActionCodeSettings
+import com.google.firebase.auth.EmailAuthProvider
+
+import com.firebase.ui.auth.ui.email.CheckEmailFragment
+import com.firebase.ui.auth.ui.email.RegisterEmailFragment
+import com.firebase.ui.auth.ui.email.EmailLinkFragment
+import com.firebase.ui.auth.ui.email.TroubleSigningInFragment
+import com.firebase.ui.auth.ui.email.WelcomeBackPasswordPrompt
+
+/**
+ * Activity to control the entire email sign up flow. Plays host to {@link CheckEmailFragment} and
+ * {@link RegisterEmailFragment} and triggers {@link WelcomeBackPasswordPrompt} and {@link
+ * WelcomeBackIdpPrompt}.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class EmailActivity : AppCompatBase(),
+ CheckEmailFragment.CheckEmailListener,
+ RegisterEmailFragment.AnonymousUpgradeListener,
+ EmailLinkFragment.TroubleSigningInListener,
+ TroubleSigningInFragment.ResendEmailListener {
+
+ private var emailLayout: TextInputLayout? = null
+
+ companion object {
+ @JvmStatic
+ fun createIntent(context: Context, flowParams: FlowParameters): Intent {
+ return createBaseIntent(context, EmailActivity::class.java, flowParams)
+ }
+
+ @JvmStatic
+ fun createIntent(context: Context, flowParams: FlowParameters, email: String?): Intent {
+ return createBaseIntent(context, EmailActivity::class.java, flowParams)
+ .putExtra(ExtraConstants.EMAIL, email)
+ }
+
+ @JvmStatic
+ fun createIntentForLinking(
+ context: Context,
+ flowParams: FlowParameters,
+ responseForLinking: IdpResponse
+ ): Intent {
+ return createIntent(context, flowParams, responseForLinking.email)
+ .putExtra(ExtraConstants.IDP_RESPONSE, responseForLinking)
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.fui_activity_register_email)
+
+ emailLayout = findViewById(R.id.email_layout)
+
+ if (savedInstanceState != null) {
+ return
+ }
+
+ // Get email from intent (can be null)
+ var email: String? = intent.extras?.getString(ExtraConstants.EMAIL)
+ val responseForLinking: IdpResponse? = intent.extras?.getParcelable(ExtraConstants.IDP_RESPONSE)
+ val user: User? = intent.extras?.getParcelable(ExtraConstants.USER)
+ if (email != null && responseForLinking != null) {
+ // Got here from WelcomeBackEmailLinkPrompt.
+ val emailConfig: AuthUI.IdpConfig = ProviderUtils.getConfigFromIdpsOrThrow(
+ getFlowParams().providers,
+ AuthUI.EMAIL_LINK_PROVIDER
+ )
+ val actionCodeSettings: ActionCodeSettings? =
+ emailConfig.getParams().getParcelable(ExtraConstants.ACTION_CODE_SETTINGS)
+ if (actionCodeSettings == null) {
+ finishOnDeveloperError(IllegalStateException("ActionCodeSettings cannot be null for email link sign in."))
+ return
+ }
+ EmailLinkPersistenceManager.getInstance().saveIdpResponseForLinking(application, responseForLinking)
+ val forceSameDevice: Boolean = emailConfig.getParams().getBoolean(ExtraConstants.FORCE_SAME_DEVICE)
+ val fragment = EmailLinkFragment.newInstance(email, actionCodeSettings, responseForLinking, forceSameDevice)
+ switchFragment(fragment, R.id.fragment_register_email, EmailLinkFragment.TAG)
+ } else {
+ var emailConfig: AuthUI.IdpConfig? = ProviderUtils.getConfigFromIdps(getFlowParams().providers, EmailAuthProvider.PROVIDER_ID)
+ if (emailConfig == null) {
+ emailConfig = ProviderUtils.getConfigFromIdps(getFlowParams().providers, AuthUI.EMAIL_LINK_PROVIDER)
+ }
+ if (emailConfig == null) {
+ finishOnDeveloperError(IllegalStateException("No email provider configured."))
+ return
+ }
+ if (emailConfig.getParams().getBoolean(ExtraConstants.ALLOW_NEW_EMAILS, true)) {
+ val ft: FragmentTransaction = supportFragmentManager.beginTransaction()
+ if (emailConfig.providerId == AuthUI.EMAIL_LINK_PROVIDER) {
+ if (email == null) {
+ finishOnDeveloperError(IllegalStateException("Email cannot be null for email link sign in."))
+ return
+ }
+ showRegisterEmailLinkFragment(emailConfig, email)
+ } else {
+ if (user == null) {
+ // Use default email from configuration if none was provided via the intent.
+ if (email == null) {
+ email = emailConfig.getParams().getString(ExtraConstants.DEFAULT_EMAIL)
+ }
+ // Pass the email (which may be null if no default is configured) to the fragment.
+ val fragment = CheckEmailFragment.newInstance(email)
+ ft.replace(R.id.fragment_register_email, fragment, CheckEmailFragment.TAG)
+ emailLayout?.let {
+ val emailFieldName = getString(R.string.fui_email_field_name)
+ ViewCompat.setTransitionName(it, emailFieldName)
+ ft.addSharedElement(it, emailFieldName)
+ }
+ ft.disallowAddToBackStack().commit()
+ return
+ }
+ val fragment = RegisterEmailFragment.newInstance(user)
+ ft.replace(R.id.fragment_register_email, fragment, RegisterEmailFragment.TAG)
+ emailLayout?.let {
+ val emailFieldName = getString(R.string.fui_email_field_name)
+ ViewCompat.setTransitionName(it, emailFieldName)
+ ft.addSharedElement(it, emailFieldName)
+ }
+ ft.disallowAddToBackStack().commit()
+ }
+ } else {
+ emailLayout?.error = getString(R.string.fui_error_email_does_not_exist)
+ }
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (requestCode == RequestCodes.WELCOME_BACK_EMAIL_FLOW ||
+ requestCode == RequestCodes.WELCOME_BACK_IDP_FLOW
+ ) {
+ finish(resultCode, data)
+ }
+ }
+
+ override fun onExistingEmailUser(user: User) {
+ if (user.providerId == AuthUI.EMAIL_LINK_PROVIDER) {
+ val emailConfig: AuthUI.IdpConfig = ProviderUtils.getConfigFromIdpsOrThrow(
+ getFlowParams().providers,
+ AuthUI.EMAIL_LINK_PROVIDER
+ )
+ val email = user.email
+ if (email == null) {
+ finishOnDeveloperError(IllegalStateException("Email cannot be null for email link sign in."))
+ return
+ }
+ showRegisterEmailLinkFragment(emailConfig, email)
+ } else {
+ startActivityForResult(
+ WelcomeBackPasswordPrompt.createIntent(this, getFlowParams(), IdpResponse.Builder(user).build()),
+ RequestCodes.WELCOME_BACK_EMAIL_FLOW
+ )
+ setSlideAnimation()
+ }
+ }
+
+ override fun onExistingIdpUser(user: User) {
+ // Existing social user: direct them to sign in using their chosen provider.
+ startActivityForResult(
+ WelcomeBackIdpPrompt.createIntent(this, getFlowParams(), user),
+ RequestCodes.WELCOME_BACK_IDP_FLOW
+ )
+ setSlideAnimation()
+ }
+
+ override fun onNewUser(user: User) {
+ // New user: direct them to create an account with email/password if account creation is enabled.
+ var emailConfig: AuthUI.IdpConfig? = ProviderUtils.getConfigFromIdps(getFlowParams().providers, EmailAuthProvider.PROVIDER_ID)
+ if (emailConfig == null) {
+ emailConfig = ProviderUtils.getConfigFromIdps(getFlowParams().providers, AuthUI.EMAIL_LINK_PROVIDER)
+ }
+ if (emailConfig == null) {
+ finishOnDeveloperError(IllegalStateException("No email provider configured."))
+ return
+ }
+ if (emailConfig.getParams().getBoolean(ExtraConstants.ALLOW_NEW_EMAILS, true)) {
+ val ft: FragmentTransaction = supportFragmentManager.beginTransaction()
+ if (emailConfig.providerId == AuthUI.EMAIL_LINK_PROVIDER) {
+ val email = user.email
+ if (email == null) {
+ finishOnDeveloperError(IllegalStateException("Email cannot be null for email link sign in."))
+ return
+ }
+ showRegisterEmailLinkFragment(emailConfig, email)
+ } else {
+ val fragment = RegisterEmailFragment.newInstance(user)
+ ft.replace(R.id.fragment_register_email, fragment, RegisterEmailFragment.TAG)
+ emailLayout?.let {
+ val emailFieldName = getString(R.string.fui_email_field_name)
+ ViewCompat.setTransitionName(it, emailFieldName)
+ ft.addSharedElement(it, emailFieldName)
+ }
+ ft.disallowAddToBackStack().commit()
+ }
+ } else {
+ emailLayout?.error = getString(R.string.fui_error_email_does_not_exist)
+ }
+ }
+
+ override fun onTroubleSigningIn(email: String) {
+ val troubleSigningInFragment = TroubleSigningInFragment.newInstance(email)
+ switchFragment(troubleSigningInFragment, R.id.fragment_register_email, TroubleSigningInFragment.TAG, true, true)
+ }
+
+ override fun onClickResendEmail(email: String) {
+ if (supportFragmentManager.backStackEntryCount > 0) {
+ // We assume that to get to TroubleSigningInFragment we went through EmailLinkFragment,
+ // which was added to the fragment back stack. To avoid needing to pop the back stack twice,
+ // we preemptively pop off the last EmailLinkFragment.
+ supportFragmentManager.popBackStack()
+ }
+ val emailConfig: AuthUI.IdpConfig = ProviderUtils.getConfigFromIdpsOrThrow(
+ getFlowParams().providers,
+ EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD
+ )
+ showRegisterEmailLinkFragment(emailConfig, email)
+ }
+
+ override fun onSendEmailFailure(e: Exception) {
+ finishOnDeveloperError(e)
+ }
+
+ override fun onDeveloperFailure(e: Exception) {
+ finishOnDeveloperError(e)
+ }
+
+ private fun finishOnDeveloperError(e: Exception) {
+ finish(
+ RESULT_CANCELED,
+ IdpResponse.getErrorIntent(FirebaseUiException(ErrorCodes.DEVELOPER_ERROR, e.message ?: "Unknown error"))
+ )
+ }
+
+ private fun setSlideAnimation() {
+ // Make the next activity slide in.
+ overridePendingTransition(R.anim.fui_slide_in_right, R.anim.fui_slide_out_left)
+ }
+
+ private fun showRegisterEmailLinkFragment(emailConfig: AuthUI.IdpConfig, email: String) {
+ val actionCodeSettings: ActionCodeSettings? = emailConfig.getParams().getParcelable(ExtraConstants.ACTION_CODE_SETTINGS)
+ if (actionCodeSettings == null) {
+ finishOnDeveloperError(IllegalStateException("ActionCodeSettings cannot be null for email link sign in."))
+ return
+ }
+ val fragment = EmailLinkFragment.newInstance(email, actionCodeSettings)
+ switchFragment(fragment, R.id.fragment_register_email, EmailLinkFragment.TAG)
+ }
+
+ override fun showProgress(@StringRes message: Int) {
+ throw UnsupportedOperationException("Email fragments must handle progress updates.")
+ }
+
+ override fun hideProgress() {
+ throw UnsupportedOperationException("Email fragments must handle progress updates.")
+ }
+
+ override fun onMergeFailure(response: IdpResponse) {
+ finish(ErrorCodes.ANONYMOUS_UPGRADE_MERGE_CONFLICT, response.toIntent())
+ }
+}
\ No newline at end of file
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/email/EmailLinkCatcherActivity.java b/auth/src/main/java/com/firebase/ui/auth/ui/email/EmailLinkCatcherActivity.java
deleted file mode 100644
index 50a7bfb0f..000000000
--- a/auth/src/main/java/com/firebase/ui/auth/ui/email/EmailLinkCatcherActivity.java
+++ /dev/null
@@ -1,140 +0,0 @@
-package com.firebase.ui.auth.ui.email;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.os.Bundle;
-
-import com.firebase.ui.auth.ErrorCodes;
-import com.firebase.ui.auth.FirebaseAuthAnonymousUpgradeException;
-import com.firebase.ui.auth.FirebaseUiException;
-import com.firebase.ui.auth.IdpResponse;
-import com.firebase.ui.auth.R;
-import com.firebase.ui.auth.data.model.FlowParameters;
-import com.firebase.ui.auth.data.model.UserCancellationException;
-import com.firebase.ui.auth.ui.InvisibleActivityBase;
-import com.firebase.ui.auth.util.ExtraConstants;
-import com.firebase.ui.auth.viewmodel.RequestCodes;
-import com.firebase.ui.auth.viewmodel.ResourceObserver;
-import com.firebase.ui.auth.viewmodel.email.EmailLinkSignInHandler;
-import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.lifecycle.ViewModelProvider;
-
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class EmailLinkCatcherActivity extends InvisibleActivityBase {
-
- private EmailLinkSignInHandler mHandler;
-
- public static Intent createIntent(Context context, FlowParameters flowParams) {
- return createBaseIntent(context, EmailLinkCatcherActivity.class, flowParams);
- }
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- initHandler();
-
- if (getFlowParams().emailLink != null) {
- mHandler.startSignIn();
- }
- }
-
- private void initHandler() {
- mHandler = new ViewModelProvider(this).get(EmailLinkSignInHandler.class);
- mHandler.init(getFlowParams());
- mHandler.getOperation().observe(this, new ResourceObserver(this) {
- @Override
- protected void onSuccess(@NonNull IdpResponse response) {
- finish(RESULT_OK, response.toIntent());
- }
-
- @Override
- protected void onFailure(@NonNull final Exception e) {
- if (e instanceof UserCancellationException) {
- finish(RESULT_CANCELED, null);
- } else if (e instanceof FirebaseAuthAnonymousUpgradeException) {
- IdpResponse res = ((FirebaseAuthAnonymousUpgradeException) e).getResponse();
- finish(RESULT_CANCELED, new Intent().putExtra(ExtraConstants.IDP_RESPONSE, res));
- } else if (e instanceof FirebaseUiException) {
- int errorCode = ((FirebaseUiException) e).getErrorCode();
- if (errorCode == ErrorCodes.EMAIL_LINK_WRONG_DEVICE_ERROR
- || errorCode == ErrorCodes.INVALID_EMAIL_LINK_ERROR
- || errorCode == ErrorCodes.EMAIL_LINK_DIFFERENT_ANONYMOUS_USER_ERROR) {
- buildAlertDialog(errorCode).show();
- } else if (errorCode == ErrorCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_ERROR
- || errorCode == ErrorCodes.EMAIL_MISMATCH_ERROR) {
- startErrorRecoveryFlow(RequestCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_FLOW);
- } else if (errorCode == ErrorCodes.EMAIL_LINK_CROSS_DEVICE_LINKING_ERROR) {
- startErrorRecoveryFlow(RequestCodes.EMAIL_LINK_CROSS_DEVICE_LINKING_FLOW);
- }
- } else if (e instanceof FirebaseAuthInvalidCredentialsException) {
- startErrorRecoveryFlow(RequestCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_FLOW);
- } else {
- finish(RESULT_CANCELED, IdpResponse.getErrorIntent(e));
- }
- }
- });
- }
-
- /**
- * @param flow must be one of RequestCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_FLOW or
- * RequestCodes.EMAIL_LINK_CROSS_DEVICE_LINKING_FLOW
- */
- private void startErrorRecoveryFlow(int flow) {
- if (flow != RequestCodes.EMAIL_LINK_CROSS_DEVICE_LINKING_FLOW
- && flow != RequestCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_FLOW) {
- throw new IllegalStateException("Invalid flow param. It must be either " +
- "RequestCodes.EMAIL_LINK_CROSS_DEVICE_LINKING_FLOW or " +
- "RequestCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_FLOW");
- }
- Intent intent = EmailLinkErrorRecoveryActivity.createIntent(getApplicationContext(),
- getFlowParams(), flow);
- startActivityForResult(intent, flow);
- }
-
- private AlertDialog buildAlertDialog(final int errorCode) {
- AlertDialog.Builder alertDialog = new AlertDialog.Builder(this);
-
- String titleText;
- String messageText;
- if (errorCode == ErrorCodes.EMAIL_LINK_DIFFERENT_ANONYMOUS_USER_ERROR) {
- titleText = getString(R.string.fui_email_link_different_anonymous_user_header);
- messageText = getString(R.string.fui_email_link_different_anonymous_user_message);
- } else if (errorCode == ErrorCodes.INVALID_EMAIL_LINK_ERROR) {
- titleText = getString(R.string.fui_email_link_invalid_link_header);
- messageText = getString(R.string.fui_email_link_invalid_link_message);
- } else {
- // Default value - ErrorCodes.EMAIL_LINK_WRONG_DEVICE_ERROR
- titleText = getString(R.string.fui_email_link_wrong_device_header);
- messageText = getString(R.string.fui_email_link_wrong_device_message);
- }
-
- return alertDialog.setTitle(titleText)
- .setMessage(messageText)
- .setPositiveButton(R.string.fui_email_link_dismiss_button,
- (dialog, id) -> finish(errorCode, null))
- .create();
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == RequestCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_FLOW
- || requestCode == RequestCodes.EMAIL_LINK_CROSS_DEVICE_LINKING_FLOW) {
- IdpResponse response = IdpResponse.fromResultIntent(data);
- // CheckActionCode is called before starting this flow, so we only get here
- // if the sign in link is valid - it can only fail by being cancelled.
- if (resultCode == RESULT_OK) {
- finish(RESULT_OK, response.toIntent());
- } else {
- finish(RESULT_CANCELED, null);
- }
- }
- }
-}
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/email/EmailLinkCatcherActivity.kt b/auth/src/main/java/com/firebase/ui/auth/ui/email/EmailLinkCatcherActivity.kt
new file mode 100644
index 000000000..0ada7cd1e
--- /dev/null
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/email/EmailLinkCatcherActivity.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.ui.email
+
+import android.app.AlertDialog
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.annotation.NonNull
+import androidx.annotation.Nullable
+import androidx.annotation.RestrictTo
+import androidx.lifecycle.ViewModelProvider
+import com.firebase.ui.auth.ErrorCodes
+import com.firebase.ui.auth.FirebaseAuthAnonymousUpgradeException
+import com.firebase.ui.auth.FirebaseUiException
+import com.firebase.ui.auth.IdpResponse
+import com.firebase.ui.auth.R
+import com.firebase.ui.auth.data.model.FlowParameters
+import com.firebase.ui.auth.data.model.UserCancellationException
+import com.firebase.ui.auth.ui.InvisibleActivityBase
+import com.firebase.ui.auth.util.ExtraConstants
+import com.firebase.ui.auth.viewmodel.RequestCodes
+import com.firebase.ui.auth.viewmodel.ResourceObserver
+import com.firebase.ui.auth.viewmodel.email.EmailLinkSignInHandler
+import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException
+
+// Assuming EmailLinkErrorRecoveryActivity exists in your project.
+import com.firebase.ui.auth.ui.email.EmailLinkErrorRecoveryActivity
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class EmailLinkCatcherActivity : InvisibleActivityBase() {
+
+ private lateinit var mHandler: EmailLinkSignInHandler
+
+ companion object {
+ @JvmStatic
+ fun createIntent(context: Context, flowParams: FlowParameters): Intent {
+ return createBaseIntent(context, EmailLinkCatcherActivity::class.java, flowParams)
+ }
+ }
+
+ override fun onCreate(@Nullable savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ initHandler()
+
+ if (getFlowParams().emailLink != null) {
+ mHandler.startSignIn()
+ }
+ }
+
+ private fun initHandler() {
+ mHandler = ViewModelProvider(this).get(EmailLinkSignInHandler::class.java)
+ mHandler.init(getFlowParams())
+ mHandler.operation.observe(this, object : ResourceObserver(this) {
+ override fun onSuccess(@NonNull response: IdpResponse) {
+ finish(RESULT_OK, response.toIntent())
+ }
+
+ override fun onFailure(@NonNull e: Exception) {
+ when {
+ e is UserCancellationException -> finish(RESULT_CANCELED, null)
+ e is FirebaseAuthAnonymousUpgradeException -> {
+ val res = e.response
+ finish(RESULT_CANCELED, Intent().putExtra(ExtraConstants.IDP_RESPONSE, res))
+ }
+ e is FirebaseUiException -> {
+ val errorCode = e.errorCode
+ when (errorCode) {
+ ErrorCodes.EMAIL_LINK_WRONG_DEVICE_ERROR,
+ ErrorCodes.INVALID_EMAIL_LINK_ERROR,
+ ErrorCodes.EMAIL_LINK_DIFFERENT_ANONYMOUS_USER_ERROR ->
+ buildAlertDialog(errorCode).show()
+ ErrorCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_ERROR,
+ ErrorCodes.EMAIL_MISMATCH_ERROR ->
+ startErrorRecoveryFlow(RequestCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_FLOW)
+ ErrorCodes.EMAIL_LINK_CROSS_DEVICE_LINKING_ERROR ->
+ startErrorRecoveryFlow(RequestCodes.EMAIL_LINK_CROSS_DEVICE_LINKING_FLOW)
+ else -> finish(RESULT_CANCELED, IdpResponse.getErrorIntent(e))
+ }
+ }
+ e is FirebaseAuthInvalidCredentialsException ->
+ startErrorRecoveryFlow(RequestCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_FLOW)
+ else -> finish(RESULT_CANCELED, IdpResponse.getErrorIntent(e))
+ }
+ }
+ })
+ }
+
+ /**
+ * @param flow must be one of RequestCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_FLOW or
+ * RequestCodes.EMAIL_LINK_CROSS_DEVICE_LINKING_FLOW
+ */
+ private fun startErrorRecoveryFlow(flow: Int) {
+ if (flow != RequestCodes.EMAIL_LINK_CROSS_DEVICE_LINKING_FLOW &&
+ flow != RequestCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_FLOW
+ ) {
+ throw IllegalStateException(
+ "Invalid flow param. It must be either " +
+ "RequestCodes.EMAIL_LINK_CROSS_DEVICE_LINKING_FLOW or " +
+ "RequestCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_FLOW"
+ )
+ }
+ val intent = EmailLinkErrorRecoveryActivity.createIntent(applicationContext, getFlowParams(), flow)
+ startActivityForResult(intent, flow)
+ }
+
+ private fun buildAlertDialog(errorCode: Int): AlertDialog {
+ val builder = AlertDialog.Builder(this)
+ val (titleText, messageText) = when (errorCode) {
+ ErrorCodes.EMAIL_LINK_DIFFERENT_ANONYMOUS_USER_ERROR -> Pair(
+ getString(R.string.fui_email_link_different_anonymous_user_header),
+ getString(R.string.fui_email_link_different_anonymous_user_message)
+ )
+ ErrorCodes.INVALID_EMAIL_LINK_ERROR -> Pair(
+ getString(R.string.fui_email_link_invalid_link_header),
+ getString(R.string.fui_email_link_invalid_link_message)
+ )
+ else -> Pair(
+ getString(R.string.fui_email_link_wrong_device_header),
+ getString(R.string.fui_email_link_wrong_device_message)
+ )
+ }
+ return builder.setTitle(titleText)
+ .setMessage(messageText)
+ .setPositiveButton(R.string.fui_email_link_dismiss_button) { _, _ ->
+ finish(errorCode, null)
+ }
+ .create()
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, @Nullable data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (requestCode == RequestCodes.EMAIL_LINK_PROMPT_FOR_EMAIL_FLOW ||
+ requestCode == RequestCodes.EMAIL_LINK_CROSS_DEVICE_LINKING_FLOW
+ ) {
+ val response = IdpResponse.fromResultIntent(data)
+ // CheckActionCode is called before starting this flow, so we only get here
+ // if the sign in link is valid – it can only fail by being cancelled.
+ if (resultCode == RESULT_OK) {
+ finish(RESULT_OK, response?.toIntent())
+ } else {
+ finish(RESULT_CANCELED, null)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/idp/AuthMethodPickerActivity.kt b/auth/src/main/java/com/firebase/ui/auth/ui/idp/AuthMethodPickerActivity.kt
index c2cc8ec32..f9df9d25f 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/idp/AuthMethodPickerActivity.kt
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/idp/AuthMethodPickerActivity.kt
@@ -83,13 +83,13 @@ import androidx.credentials.PasswordCredential
import androidx.credentials.PublicKeyCredential
import androidx.credentials.exceptions.GetCredentialException
-import com.firebase.ui.auth.AuthUI.EMAIL_LINK_PROVIDER
-import com.firebase.ui.auth.util.ExtraConstants.GENERIC_OAUTH_BUTTON_ID
-import com.firebase.ui.auth.util.ExtraConstants.GENERIC_OAUTH_PROVIDER_ID
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
import com.google.firebase.auth.GoogleAuthCredential
+import com.firebase.ui.auth.util.ExtraConstants.GENERIC_OAUTH_BUTTON_ID
+import com.firebase.ui.auth.util.ExtraConstants.GENERIC_OAUTH_PROVIDER_ID
+import com.firebase.ui.auth.AuthUI.Companion.EMAIL_LINK_PROVIDER
@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP)
class AuthMethodPickerActivity : AppCompatBase() {
@@ -335,8 +335,8 @@ class AuthMethodPickerActivity : AppCompatBase() {
PhoneAuthProvider.PROVIDER_ID -> R.layout.fui_provider_button_phone
AuthUI.ANONYMOUS_PROVIDER -> R.layout.fui_provider_button_anonymous
else -> {
- if (!TextUtils.isEmpty(idpConfig.params.getString(GENERIC_OAUTH_PROVIDER_ID))) {
- idpConfig.params.getInt(GENERIC_OAUTH_BUTTON_ID)
+ if (!TextUtils.isEmpty(idpConfig.getParams().getString(GENERIC_OAUTH_PROVIDER_ID))) {
+ idpConfig.getParams().getInt(GENERIC_OAUTH_BUTTON_ID)
} else {
throw IllegalStateException("Unknown provider: ${idpConfig.providerId}")
}
@@ -391,7 +391,7 @@ class AuthMethodPickerActivity : AppCompatBase() {
AuthUI.ANONYMOUS_PROVIDER ->
viewModelProvider.get(AnonymousSignInHandler::class.java).initWith(flowParams)
GoogleAuthProvider.PROVIDER_ID ->
- if (authUI.isUseEmulator) {
+ if (authUI.isUseEmulator()) {
viewModelProvider.get(GenericIdpSignInHandler::class.java)
.initWith(GenericIdpSignInHandler.getGenericGoogleConfig())
} else {
@@ -399,14 +399,14 @@ class AuthMethodPickerActivity : AppCompatBase() {
.initWith(GoogleSignInHandler.Params(idpConfig))
}
FacebookAuthProvider.PROVIDER_ID ->
- if (authUI.isUseEmulator) {
+ if (authUI.isUseEmulator()) {
viewModelProvider.get(GenericIdpSignInHandler::class.java)
.initWith(GenericIdpSignInHandler.getGenericFacebookConfig())
} else {
viewModelProvider.get(FacebookSignInHandler::class.java).initWith(idpConfig)
}
else -> {
- if (!TextUtils.isEmpty(idpConfig.params.getString(GENERIC_OAUTH_PROVIDER_ID))) {
+ if (!TextUtils.isEmpty(idpConfig.getParams().getString(GENERIC_OAUTH_PROVIDER_ID))) {
viewModelProvider.get(GenericIdpSignInHandler::class.java).initWith(idpConfig)
} else {
throw IllegalStateException("Unknown provider: $providerId")
@@ -434,7 +434,7 @@ class AuthMethodPickerActivity : AppCompatBase() {
private fun handleResponse(response: IdpResponse) {
// For social providers (unless using an emulator) use the social response handler.
- val isSocialResponse = AuthUI.SOCIAL_PROVIDERS.contains(providerId) && !authUI.isUseEmulator
+ val isSocialResponse = AuthUI.isSocialProvider(providerId) && !authUI.isUseEmulator()
if (!response.isSuccessful) {
mHandler.startSignIn(response)
} else if (isSocialResponse) {
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/idp/SingleSignInActivity.java b/auth/src/main/java/com/firebase/ui/auth/ui/idp/SingleSignInActivity.java
index 17221f7ed..dec97c33e 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/idp/SingleSignInActivity.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/idp/SingleSignInActivity.java
@@ -93,7 +93,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
mProvider.getOperation().observe(this, new ResourceObserver(this) {
@Override
protected void onSuccess(@NonNull IdpResponse response) {
- boolean useSocialHandler = AuthUI.SOCIAL_PROVIDERS.contains(provider)
+ boolean useSocialHandler = AuthUI.isSocialProvider(provider)
&& !getAuthUI().isUseEmulator();
if (useSocialHandler || !response.isSuccessful()) {
diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/idp/WelcomeBackIdpPrompt.java b/auth/src/main/java/com/firebase/ui/auth/ui/idp/WelcomeBackIdpPrompt.java
index 0f3e2319c..a63b1a459 100644
--- a/auth/src/main/java/com/firebase/ui/auth/ui/idp/WelcomeBackIdpPrompt.java
+++ b/auth/src/main/java/com/firebase/ui/auth/ui/idp/WelcomeBackIdpPrompt.java
@@ -153,7 +153,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
@Override
protected void onSuccess(@NonNull IdpResponse response) {
boolean isGenericIdp = getAuthUI().isUseEmulator()
- || !AuthUI.SOCIAL_PROVIDERS.contains(response.getProviderType());
+ || !AuthUI.isSocialProvider(response.getProviderType());
if (isGenericIdp
&& !response.hasCredentialForLinking()
diff --git a/auth/src/main/java/com/firebase/ui/auth/viewmodel/email/WelcomeBackPasswordHandler.java b/auth/src/main/java/com/firebase/ui/auth/viewmodel/email/WelcomeBackPasswordHandler.java
index d68b93b39..75ae30133 100644
--- a/auth/src/main/java/com/firebase/ui/auth/viewmodel/email/WelcomeBackPasswordHandler.java
+++ b/auth/src/main/java/com/firebase/ui/auth/viewmodel/email/WelcomeBackPasswordHandler.java
@@ -72,7 +72,7 @@ public void startSignIn(@NonNull final String email,
final AuthCredential credToValidate = EmailAuthProvider.getCredential(email, password);
// Check to see if we need to link (for social providers with the same email)
- if (AuthUI.SOCIAL_PROVIDERS.contains(inputResponse.getProviderType())) {
+ if (AuthUI.isSocialProvider(inputResponse.getProviderType())) {
// Add the provider to the same account before triggering a merge failure.
authOperationManager.safeLink(credToValidate, credential, getArguments())
.addOnSuccessListener(result -> handleMergeFailure(credToValidate))
diff --git a/auth/src/main/java/com/firebase/ui/auth/viewmodel/idp/LinkingSocialProviderResponseHandler.java b/auth/src/main/java/com/firebase/ui/auth/viewmodel/idp/LinkingSocialProviderResponseHandler.java
index 5e5b1def9..b03092b35 100644
--- a/auth/src/main/java/com/firebase/ui/auth/viewmodel/idp/LinkingSocialProviderResponseHandler.java
+++ b/auth/src/main/java/com/firebase/ui/auth/viewmodel/idp/LinkingSocialProviderResponseHandler.java
@@ -128,7 +128,7 @@ public boolean hasCredentialForLinking() {
private boolean isGenericIdpLinkingFlow(@NonNull String providerId) {
// TODO(lsirac): Remove use of SUPPORTED_OAUTH_PROVIDERS when we decide to support all IDPs
- return AuthUI.SUPPORTED_OAUTH_PROVIDERS.contains(providerId)
+ return AuthUI.isSupportedOAuthProvider(providerId)
&& mRequestedSignInCredential != null
&& getAuth().getCurrentUser() != null
&& !getAuth().getCurrentUser().isAnonymous();