Skip to content

Allow most auth flows without Play services #1865

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Allow most auth flows on devices without Google Play services (#1858)
36 changes: 31 additions & 5 deletions auth/src/main/java/com/firebase/ui/auth/AuthUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@
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.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.GoogleApi;
import com.google.android.gms.common.api.Scope;
import com.google.android.gms.tasks.Continuation;
import com.google.android.gms.tasks.Task;
Expand Down Expand Up @@ -340,6 +343,12 @@ public Task<AuthResult> silentSignIn(@NonNull Context context,
.getParcelable(ExtraConstants.GOOGLE_SIGN_IN_OPTIONS);
}

// If Play services are not available we can't attempt to use the credentials client.
if (!GoogleApiUtils.isPlayServicesAvailable(context)) {
return Tasks.forException(
new FirebaseUiException(ErrorCodes.PLAY_SERVICES_UPDATE_CANCELLED));
}

return GoogleApiUtils.getCredentialsClient(context)
.request(new CredentialRequest.Builder()
// We can support both email and Google at the same time here because they
Expand Down Expand Up @@ -392,8 +401,16 @@ public Task<AuthResult> then(
*/
@NonNull
public Task<Void> signOut(@NonNull Context context) {
Task<Void> maybeDisableAutoSignIn = GoogleApiUtils.getCredentialsClient(context)
.disableAutoSignIn()
boolean playServicesAvailable = GoogleApiUtils.isPlayServicesAvailable(context);
if (!playServicesAvailable) {
Log.w(TAG, "Google Play services not available during signOut");
}

Task<Void> maybeDisableAutoSignIn = playServicesAvailable
? GoogleApiUtils.getCredentialsClient(context).disableAutoSignIn()
: Tasks.forResult((Void) null);

maybeDisableAutoSignIn
.continueWith(new Continuation<Void, Void>() {
@Override
public Void then(@NonNull Task<Void> task) {
Expand Down Expand Up @@ -434,7 +451,7 @@ public Void then(@NonNull Task<Void> task) {
* @param context the calling {@link Context}.
*/
@NonNull
public Task<Void> delete(@NonNull Context context) {
public Task<Void> delete(@NonNull final Context context) {
final FirebaseUser currentUser = mAuth.getCurrentUser();
if (currentUser == null) {
return Tasks.forException(new FirebaseAuthInvalidUserException(
Expand All @@ -443,14 +460,19 @@ public Task<Void> delete(@NonNull Context context) {
}

final List<Credential> credentials = getCredentialsFromFirebaseUser(currentUser);
final CredentialsClient client = GoogleApiUtils.getCredentialsClient(context);

// Ensure the order in which tasks are executed properly destructures the user.
return signOutIdps(context).continueWithTask(new Continuation<Void, Task<Void>>() {
@Override
public Task<Void> then(@NonNull Task<Void> task) {
task.getResult(); // Propagate exception if there was one

if (!GoogleApiUtils.isPlayServicesAvailable(context)) {
Log.w(TAG, "Google Play services not available during delete");
return Tasks.forResult((Void) null);
}

final CredentialsClient client = GoogleApiUtils.getCredentialsClient(context);
List<Task<?>> credentialTasks = new ArrayList<>();
for (Credential credential : credentials) {
credentialTasks.add(client.delete(credential));
Expand Down Expand Up @@ -516,7 +538,11 @@ private Task<Void> signOutIdps(@NonNull Context context) {
if (ProviderAvailability.IS_FACEBOOK_AVAILABLE) {
LoginManager.getInstance().logOut();
}
return GoogleSignIn.getClient(context, GoogleSignInOptions.DEFAULT_SIGN_IN).signOut();
if (GoogleApiUtils.isPlayServicesAvailable(context)) {
return GoogleSignIn.getClient(context, GoogleSignInOptions.DEFAULT_SIGN_IN).signOut();
} else {
return Tasks.forResult((Void) null);
}
}

/**
Expand Down
10 changes: 8 additions & 2 deletions auth/src/main/java/com/firebase/ui/auth/KickoffActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.auth.GoogleAuthProvider;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand Down Expand Up @@ -53,8 +56,11 @@ protected void onFailure(@NonNull Exception e) {
}
});

GoogleApiAvailability.getInstance()
.makeGooglePlayServicesAvailable(this)
Task<Void> checkPlayServicesTask = getFlowParams().isPlayServicesRequired()
? GoogleApiAvailability.getInstance().makeGooglePlayServicesAvailable(this)
: Tasks.forResult((Void) null);

checkPlayServicesTask
.addOnSuccessListener(this, new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
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;
Expand Down Expand Up @@ -200,6 +202,23 @@ 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)
|| enableHints
|| 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import com.google.android.gms.auth.api.credentials.Credentials;
import com.google.android.gms.auth.api.credentials.CredentialsClient;
import com.google.android.gms.auth.api.credentials.CredentialsOptions;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;

import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
Expand All @@ -16,6 +18,12 @@ private GoogleApiUtils() {
throw new AssertionError("No instance for you!");
}

public static boolean isPlayServicesAvailable(@NonNull Context context) {
return GoogleApiAvailability
.getInstance()
.isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS;
}

@NonNull
public static CredentialsClient getCredentialsClient(@NonNull Context context) {
CredentialsOptions options = new CredentialsOptions.Builder()
Expand Down