Skip to content

Commit 0da111b

Browse files
SUPERCILEXsamtstern
authored andcommitted
Rank providers by usability when picking top provider (#1362)
1 parent 2274696 commit 0da111b

13 files changed

+78
-55
lines changed

auth/src/main/java/com/firebase/ui/auth/data/model/FlowParameters.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class FlowParameters implements Parcelable {
4040
public final String appName;
4141

4242
@NonNull
43-
public final List<IdpConfig> providerInfo;
43+
public final List<IdpConfig> providers;
4444

4545
@StyleRes
4646
public final int themeId;
@@ -60,7 +60,7 @@ public class FlowParameters implements Parcelable {
6060

6161
public FlowParameters(
6262
@NonNull String appName,
63-
@NonNull List<IdpConfig> providerInfo,
63+
@NonNull List<IdpConfig> providers,
6464
@StyleRes int themeId,
6565
@DrawableRes int logoId,
6666
@Nullable String termsOfServiceUrl,
@@ -69,8 +69,8 @@ public FlowParameters(
6969
boolean enableHints,
7070
boolean enableAnonymousUpgrade) {
7171
this.appName = Preconditions.checkNotNull(appName, "appName cannot be null");
72-
this.providerInfo = Collections.unmodifiableList(
73-
Preconditions.checkNotNull(providerInfo, "providerInfo cannot be null"));
72+
this.providers = Collections.unmodifiableList(
73+
Preconditions.checkNotNull(providers, "providers cannot be null"));
7474
this.themeId = themeId;
7575
this.logoId = logoId;
7676
this.termsOfServiceUrl = termsOfServiceUrl;
@@ -90,7 +90,7 @@ public static FlowParameters fromIntent(Intent intent) {
9090
@Override
9191
public void writeToParcel(Parcel dest, int flags) {
9292
dest.writeString(appName);
93-
dest.writeTypedList(providerInfo);
93+
dest.writeTypedList(providers);
9494
dest.writeInt(themeId);
9595
dest.writeInt(logoId);
9696
dest.writeString(termsOfServiceUrl);
@@ -137,7 +137,7 @@ public FlowParameters[] newArray(int size) {
137137
};
138138

139139
public boolean isSingleProviderFlow() {
140-
return providerInfo.size() == 1;
140+
return providers.size() == 1;
141141
}
142142

143143
public boolean isTermsOfServiceUrlProvided() {

auth/src/main/java/com/firebase/ui/auth/data/remote/SignInKickstarter.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public SignInKickstarter(Application application) {
5656
public void start() {
5757
// Only support password credentials if email auth is enabled
5858
boolean supportPasswords = ProviderUtils.getConfigFromIdps(
59-
getArguments().providerInfo, EmailAuthProvider.PROVIDER_ID) != null;
59+
getArguments().providers, EmailAuthProvider.PROVIDER_ID) != null;
6060
List<String> accountTypes = getCredentialAccountTypes();
6161

6262
// If the request will be empty, avoid the step entirely
@@ -97,7 +97,7 @@ public void onComplete(@NonNull Task<CredentialRequestResponse> task) {
9797
private void startAuthMethodChoice() {
9898
// If there is only one provider selected, launch the flow directly
9999
if (getArguments().isSingleProviderFlow()) {
100-
AuthUI.IdpConfig firstIdpConfig = getArguments().providerInfo.get(0);
100+
AuthUI.IdpConfig firstIdpConfig = getArguments().providers.get(0);
101101
String firstProvider = firstIdpConfig.getProviderId();
102102
switch (firstProvider) {
103103
case EmailAuthProvider.PROVIDER_ID:
@@ -157,7 +157,7 @@ private void redirectSignIn(String provider, String id) {
157157

158158
private List<String> getCredentialAccountTypes() {
159159
List<String> accounts = new ArrayList<>();
160-
for (AuthUI.IdpConfig idpConfig : getArguments().providerInfo) {
160+
for (AuthUI.IdpConfig idpConfig : getArguments().providers) {
161161
@AuthUI.SupportedProvider String providerId = idpConfig.getProviderId();
162162
if (providerId.equals(GoogleAuthProvider.PROVIDER_ID)) {
163163
accounts.add(ProviderUtils.providerIdToAccountType(providerId));

auth/src/main/java/com/firebase/ui/auth/ui/email/CheckEmailHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public void fetchCredential() {
3333

3434
public void fetchProvider(final String email) {
3535
setResult(Resource.<User>forLoading());
36-
ProviderUtils.fetchTopProvider(getAuth(), email)
36+
ProviderUtils.fetchTopProvider(getAuth(), getArguments(), email)
3737
.addOnCompleteListener(new OnCompleteListener<String>() {
3838
@Override
3939
public void onComplete(@NonNull Task<String> task) {
@@ -53,7 +53,7 @@ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent d
5353
setResult(Resource.<User>forLoading());
5454
final Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
5555
final String email = credential.getId();
56-
ProviderUtils.fetchTopProvider(getAuth(), email)
56+
ProviderUtils.fetchTopProvider(getAuth(), getArguments(), email)
5757
.addOnCompleteListener(new OnCompleteListener<String>() {
5858
@Override
5959
public void onComplete(@NonNull Task<String> task) {

auth/src/main/java/com/firebase/ui/auth/ui/email/EmailActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public void onNewUser(User user) {
111111
TextInputLayout emailLayout = findViewById(R.id.email_layout);
112112

113113
AuthUI.IdpConfig emailConfig = ProviderUtils.getConfigFromIdpsOrThrow(
114-
getFlowParams().providerInfo, EmailAuthProvider.PROVIDER_ID);
114+
getFlowParams().providers, EmailAuthProvider.PROVIDER_ID);
115115
if (emailConfig.getParams().getBoolean(ExtraConstants.ALLOW_NEW_EMAILS, true)) {
116116
RegisterEmailFragment fragment = RegisterEmailFragment.newInstance(user);
117117
FragmentTransaction ft = getSupportFragmentManager().beginTransaction()

auth/src/main/java/com/firebase/ui/auth/ui/email/RegisterEmailFragment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
147147

148148
// Get configuration
149149
AuthUI.IdpConfig emailConfig = ProviderUtils.getConfigFromIdpsOrThrow(
150-
getFlowParams().providerInfo, EmailAuthProvider.PROVIDER_ID);
150+
getFlowParams().providers, EmailAuthProvider.PROVIDER_ID);
151151
boolean requireName = emailConfig.getParams()
152152
.getBoolean(ExtraConstants.REQUIRE_NAME, true);
153153
mPasswordFieldValidator = new PasswordFieldValidator(

auth/src/main/java/com/firebase/ui/auth/ui/idp/AuthMethodPickerActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
8989
mHandler = ViewModelProviders.of(this).get(SocialProviderResponseHandler.class);
9090
mHandler.init(params);
9191

92-
populateIdpList(params.providerInfo, mHandler);
92+
populateIdpList(params.providers, mHandler);
9393

9494
int logoId = params.logoId;
9595
if (logoId == AuthUI.NO_LOGO) {

auth/src/main/java/com/firebase/ui/auth/ui/idp/SingleSignInActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
4747
String provider = user.getProviderId();
4848

4949
AuthUI.IdpConfig providerConfig =
50-
ProviderUtils.getConfigFromIdps(getFlowParams().providerInfo, provider);
50+
ProviderUtils.getConfigFromIdps(getFlowParams().providers, provider);
5151
if (providerConfig == null) {
5252
finish(RESULT_CANCELED, IdpResponse.getErrorIntent(new FirebaseUiException(
5353
ErrorCodes.DEVELOPER_ERROR,

auth/src/main/java/com/firebase/ui/auth/ui/idp/WelcomeBackIdpPrompt.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
9898

9999
String providerId = existingUser.getProviderId();
100100
AuthUI.IdpConfig config =
101-
ProviderUtils.getConfigFromIdps(getFlowParams().providerInfo, providerId);
101+
ProviderUtils.getConfigFromIdps(getFlowParams().providers, providerId);
102102
if (config == null) {
103103
finish(RESULT_CANCELED, IdpResponse.getErrorIntent(new FirebaseUiException(
104104
ErrorCodes.DEVELOPER_ERROR,

auth/src/main/java/com/firebase/ui/auth/util/data/ProviderUtils.java

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
import com.firebase.ui.auth.AuthUI;
2323
import com.firebase.ui.auth.IdpResponse;
24-
import com.firebase.ui.auth.util.Preconditions;
24+
import com.firebase.ui.auth.data.model.FlowParameters;
2525
import com.google.android.gms.auth.api.credentials.IdentityProviders;
2626
import com.google.android.gms.tasks.Continuation;
2727
import com.google.android.gms.tasks.Task;
@@ -36,6 +36,7 @@
3636
import com.google.firebase.auth.SignInMethodQueryResult;
3737
import com.google.firebase.auth.TwitterAuthProvider;
3838

39+
import java.util.ArrayList;
3940
import java.util.List;
4041

4142
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -154,41 +155,63 @@ public static AuthUI.IdpConfig getConfigFromIdpsOrThrow(List<AuthUI.IdpConfig> i
154155
return config;
155156
}
156157

157-
public static Task<String> fetchTopProvider(FirebaseAuth auth, @NonNull String email) {
158+
public static Task<List<String>> fetchSortedProviders(@NonNull FirebaseAuth auth,
159+
@NonNull final FlowParameters params,
160+
@NonNull String email) {
158161
if (TextUtils.isEmpty(email)) {
159162
return Tasks.forException(new NullPointerException("Email cannot be empty"));
160163
}
161164

162165
return auth.fetchSignInMethodsForEmail(email)
163-
.continueWith(new Continuation<SignInMethodQueryResult, String>() {
166+
.continueWith(new Continuation<SignInMethodQueryResult, List<String>>() {
164167
@Override
165-
public String then(@NonNull Task<SignInMethodQueryResult> task) {
166-
if (!task.isSuccessful()) return null;
167-
168+
public List<String> then(@NonNull Task<SignInMethodQueryResult> task) {
168169
List<String> methods = task.getResult().getSignInMethods();
169-
return methods == null || methods.isEmpty()
170-
? null : methods.get(methods.size() - 1);
170+
if (methods == null) { methods = new ArrayList<>(); }
171+
172+
List<String> allowedProviders = new ArrayList<>(params.providers.size());
173+
for (AuthUI.IdpConfig provider : params.providers) {
174+
allowedProviders.add(provider.getProviderId());
175+
}
176+
177+
List<String> lastSignedInProviders = new ArrayList<>(methods.size());
178+
for (String method : methods) {
179+
String id = signInMethodToProviderId(method);
180+
if (allowedProviders.contains(id)) {
181+
lastSignedInProviders.add(0, id);
182+
}
183+
}
184+
185+
// Reorder providers from most to least usable. Usability is determined by
186+
// how many steps a user needs to perform to log in.
187+
maximizePriority(lastSignedInProviders, GoogleAuthProvider.PROVIDER_ID);
188+
189+
return lastSignedInProviders;
190+
}
191+
192+
private void maximizePriority(List<String> providers, String id) {
193+
if (providers.remove(id)) { providers.add(0, id); }
171194
}
172-
}).continueWith(new Continuation<String, String>() {
195+
});
196+
}
197+
198+
public static Task<String> fetchTopProvider(
199+
@NonNull FirebaseAuth auth,
200+
@NonNull FlowParameters params,
201+
@NonNull String email) {
202+
return fetchSortedProviders(auth, params, email)
203+
.continueWith(new Continuation<List<String>, String>() {
173204
@Override
174-
public String then(@NonNull Task<String> task) {
175-
String method = task.getResult();
176-
if (method == null) {
205+
public String then(@NonNull Task<List<String>> task) {
206+
if (!task.isSuccessful()) return null;
207+
List<String> providers = task.getResult();
208+
209+
if (providers.isEmpty()) {
177210
return null;
178211
} else {
179-
return signInMethodToProviderId(method);
212+
return providers.get(0);
180213
}
181214
}
182215
});
183216
}
184-
185-
public static String getTopProvider(@NonNull List<String> providers) {
186-
return providers == null || providers.isEmpty() ? null :
187-
providers.get(providers.size() - 1);
188-
}
189-
190-
public static boolean isExistingProvider(@NonNull List<String> providers, String provider) {
191-
if (providers == null) throw new IllegalArgumentException("The list of providers is null.");
192-
return providers.contains(provider);
193-
}
194217
}

auth/src/main/java/com/firebase/ui/auth/viewmodel/email/EmailProviderResponseHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public void onFailure(@NonNull Exception e) {
6969
// Collision with existing user email without anonymous upgrade
7070
// it should be very hard for the user to even get to this error
7171
// due to CheckEmailFragment.
72-
ProviderUtils.fetchTopProvider(getAuth(), email)
72+
ProviderUtils.fetchTopProvider(getAuth(), getArguments(), email)
7373
.addOnSuccessListener(new StartWelcomeBackFlow(email))
7474
.addOnFailureListener(new OnFailureListener() {
7575
@Override

auth/src/main/java/com/firebase/ui/auth/viewmodel/idp/SocialProviderResponseHandler.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import com.google.firebase.auth.AuthResult;
2828
import com.google.firebase.auth.EmailAuthProvider;
2929
import com.google.firebase.auth.FirebaseAuthUserCollisionException;
30-
import com.google.firebase.auth.SignInMethodQueryResult;
3130

3231
import java.util.List;
3332

@@ -64,7 +63,7 @@ public void onSuccess(AuthResult result) {
6463
})
6564
.addOnFailureListener(new OnFailureListener() {
6665
@Override
67-
public void onFailure(@NonNull final Exception e) {
66+
public void onFailure(@NonNull Exception e) {
6867
if (e instanceof FirebaseAuthUserCollisionException) {
6968
final String email = response.getEmail();
7069
if (email == null) {
@@ -80,20 +79,22 @@ public void onFailure(@NonNull final Exception e) {
8079
// existing user.
8180
// CASE 3: CASE 2 with an anonymous user. We link the new IDP to the
8281
// same account before handling invoking a merge failure.
83-
getAuth().fetchSignInMethodsForEmail(email)
84-
.addOnSuccessListener(new OnSuccessListener<SignInMethodQueryResult>() {
82+
ProviderUtils.fetchSortedProviders(getAuth(), getArguments(), email)
83+
.addOnSuccessListener(new OnSuccessListener<List<String>>() {
8584
@Override
86-
public void onSuccess(SignInMethodQueryResult result) {
87-
List<String> providers = result.getSignInMethods();
88-
if (ProviderUtils.isExistingProvider(providers,
89-
response.getProviderType())) {
85+
public void onSuccess(List<String> providers) {
86+
if (providers.contains(response.getProviderType())) {
9087
// Case 1
9188
handleMergeFailure(credential);
89+
} else if (providers.isEmpty()) {
90+
setResult(Resource.<IdpResponse>forFailure(
91+
new FirebaseUiException(
92+
ErrorCodes.DEVELOPER_ERROR,
93+
"No supported providers.")));
9294
} else {
9395
// Case 2 & 3 - we need to link
9496
startWelcomeBackFlowForLinking(
95-
ProviderUtils.getTopProvider(providers),
96-
response);
97+
providers.get(0), response);
9798
}
9899
}
99100
})

auth/src/test/java/com/firebase/ui/auth/AuthUITest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ public void testCreateStartIntent_shouldHaveEmailAsDefaultProvider() {
4949
.createSignInIntentBuilder()
5050
.build()
5151
.getParcelableExtra(ExtraConstants.FLOW_PARAMS);
52-
assertEquals(1, flowParameters.providerInfo.size());
52+
assertEquals(1, flowParameters.providers.size());
5353
assertEquals(EmailAuthProvider.PROVIDER_ID,
54-
flowParameters.providerInfo.get(0).getProviderId());
54+
flowParameters.providers.get(0).getProviderId());
5555
}
5656

5757
@Test(expected = IllegalArgumentException.class)
@@ -75,7 +75,7 @@ public void testCreatingStartIntent() {
7575
.build()
7676
.getParcelableExtra(ExtraConstants.FLOW_PARAMS);
7777

78-
assertEquals(4, flowParameters.providerInfo.size());
78+
assertEquals(4, flowParameters.providers.size());
7979
assertEquals(TestHelper.MOCK_APP.getName(), flowParameters.appName);
8080
assertEquals(TestConstants.TOS_URL, flowParameters.termsOfServiceUrl);
8181
assertEquals(TestConstants.PRIVACY_URL, flowParameters.privacyPolicyUrl);

auth/src/test/java/com/firebase/ui/auth/viewmodel/SocialProviderResponseHandlerTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,7 @@ public void testSignInIdp_anonymousUserUpgradeEnabledAndExistingPasswordUserWith
311311

312312
private void setupAnonymousUpgrade() {
313313
// enableAnonymousUpgrade must be set to true
314-
FlowParameters testParams = TestHelper.getFlowParameters(Collections.singletonList(
315-
EmailAuthProvider.PROVIDER_ID), /* enableAnonymousUpgrade */ true);
314+
FlowParameters testParams = TestHelper.getFlowParameters(AuthUI.SUPPORTED_PROVIDERS, /* enableAnonymousUpgrade */ true);
316315
mHandler.initializeForTesting(testParams, mMockAuth, null, null);
317316

318317
when(mUser.isAnonymous()).thenReturn(true);

0 commit comments

Comments
 (0)