Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
a1c7dda
Prep for final refactors
SUPERCILEX Mar 15, 2018
3325fda
Perf improvements
SUPERCILEX Mar 16, 2018
fde1bca
Fix loose ends
SUPERCILEX Mar 16, 2018
6f0e2c9
Start addressing review feedback
SUPERCILEX Mar 16, 2018
624aa6e
Move usage updates into getters
SUPERCILEX Mar 16, 2018
3acea2d
Merge improvements from future PRs
SUPERCILEX Mar 16, 2018
dd9ae69
Address review feedback
SUPERCILEX Mar 16, 2018
887ae0d
Fancisize tests
SUPERCILEX Mar 16, 2018
fdb9f94
Completely rewrite provider login with new architecture
SUPERCILEX Mar 16, 2018
5511513
Cleanup and pre-testing bug fixes
SUPERCILEX Mar 16, 2018
1be0b8b
Make lint happy
SUPERCILEX Mar 16, 2018
0a65776
Fix tests
SUPERCILEX Mar 16, 2018
f0dc341
It actually works!
SUPERCILEX Mar 16, 2018
03efdea
Kill SignInDelegate ☠️
SUPERCILEX Mar 16, 2018
057c561
Merge remote-tracking branch 'upstream/version-3.3.0-dev' into providers
SUPERCILEX Mar 16, 2018
7736254
Thank you TDD!
SUPERCILEX Mar 16, 2018
4c2eec6
Start addressing review feedback
SUPERCILEX Mar 16, 2018
6bf202d
Restore previous linking behavior
SUPERCILEX Mar 16, 2018
3e20621
Merge branch 'providers' into kickstarter
SUPERCILEX Mar 16, 2018
8360dc6
Tidy
SUPERCILEX Mar 16, 2018
705b9b9
Restore previous linking behavior
SUPERCILEX Mar 16, 2018
0f9155c
Merge branch 'providers' into kickstarter
SUPERCILEX Mar 16, 2018
e17c101
Fix bugs found in real-world testing plus cleanup
SUPERCILEX Mar 16, 2018
27ff4d3
Add hack for https://github.com/googlesamples/google-services/issues/345
SUPERCILEX Mar 16, 2018
8732c11
Fix WelcomeBackIdpPrompt recursively attempting sign-ins with the sam…
SUPERCILEX Mar 16, 2018
5f46726
Merge branch 'providers' into kickstarter
SUPERCILEX Mar 17, 2018
80c9422
Cleanup
SUPERCILEX Mar 17, 2018
95b024c
Tidy loose ends
SUPERCILEX Mar 17, 2018
d121e08
Merge branch 'providers' into kickstarter
SUPERCILEX Mar 17, 2018
562689d
Fix merge mistakes
SUPERCILEX Mar 17, 2018
d9540b1
More cleanup
SUPERCILEX Mar 17, 2018
e233c82
Merge remote-tracking branch 'upstream/version-3.3.0-dev' into providers
SUPERCILEX Mar 19, 2018
3b901c5
Address review feedback
SUPERCILEX Mar 20, 2018
e79388f
Make Twitter initialization prettier
SUPERCILEX Mar 20, 2018
82d6af6
Rename ProvidersHandlerBase
SUPERCILEX Mar 20, 2018
ff61518
Slightly cleaner user cancellation error message no-op
SUPERCILEX Mar 20, 2018
1896c82
Merge branch 'providers' into kickstarter
SUPERCILEX Mar 20, 2018
b1f2356
Merge remote-tracking branch 'upstream/version-3.3.0-dev' into providers
SUPERCILEX Mar 21, 2018
623a9c3
Merge branch 'providers' into kickstarter
SUPERCILEX Mar 21, 2018
229672f
Fix merge mistakes
SUPERCILEX Mar 21, 2018
6f5207a
Better optional provider support
SUPERCILEX Mar 21, 2018
bbc00f0
Implement new resource usability logic
SUPERCILEX Mar 22, 2018
e8214a4
Address some more feedback
SUPERCILEX Mar 22, 2018
6b8eb7f
Add back unused tools declaration in strings
SUPERCILEX Mar 22, 2018
43a1d31
Simplify provider dependency tree
SUPERCILEX Mar 22, 2018
84363c1
Merge branch 'providers' into kickstarter
SUPERCILEX Mar 22, 2018
f7ac429
Fix merge mistakes
SUPERCILEX Mar 22, 2018
748cbc5
Forgot to make all exceptions be usable
SUPERCILEX Mar 22, 2018
9ead908
Merge branch 'providers' into kickstarter
SUPERCILEX Mar 22, 2018
2fad171
Minor cleanup
SUPERCILEX Mar 22, 2018
368a6b3
Fix pressing back closing all activities
SUPERCILEX Mar 22, 2018
e679f80
Address comments
SUPERCILEX Mar 22, 2018
6fec29c
Add back strings and suppress lint
SUPERCILEX Mar 22, 2018
f4ec251
More simplification
SUPERCILEX Mar 22, 2018
e947030
Squishify the provider classes together
SUPERCILEX Mar 22, 2018
36a41f2
Fix welcome back crash if response failed
SUPERCILEX Mar 22, 2018
41b9a5e
Merge branch 'providers' into kickstarter
SUPERCILEX Mar 22, 2018
722f9c6
Consistency
SUPERCILEX Mar 22, 2018
4f223c5
Merge branch 'providers' into kickstarter
SUPERCILEX Mar 22, 2018
f92cdd3
Merge remote-tracking branch 'upstream/version-3.3.0-dev' into kickst…
SUPERCILEX Mar 23, 2018
e466caa
Remove now unused code
SUPERCILEX Mar 23, 2018
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
98 changes: 60 additions & 38 deletions auth/src/main/java/com/firebase/ui/auth/KickoffActivity.java
Original file line number Diff line number Diff line change
@@ -1,91 +1,113 @@
package com.firebase.ui.auth;

import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.support.annotation.RestrictTo;
import android.util.Log;

import com.firebase.ui.auth.data.model.FlowParameters;
import com.firebase.ui.auth.data.model.Resource;
import com.firebase.ui.auth.data.model.State;
import com.firebase.ui.auth.data.model.UserCancellationException;
import com.firebase.ui.auth.data.remote.SignInKickstarter;
import com.firebase.ui.auth.ui.HelperActivityBase;
import com.firebase.ui.auth.util.ExtraConstants;
import com.firebase.ui.auth.util.PlayServicesHelper;
import com.firebase.ui.auth.util.signincontainer.SignInDelegate;
import com.firebase.ui.auth.util.ui.FlowUtils;
import com.firebase.ui.auth.viewmodel.RequestCodes;

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class KickoffActivity extends HelperActivityBase {
private static final String TAG = "KickoffActivity";
private static final String IS_WAITING_FOR_PLAY_SERVICES = "is_waiting_for_play_services";

private SignInKickstarter mKickstarter;
private boolean mIsWaitingForPlayServices = false;

public static Intent createIntent(Context context, FlowParameters flowParams) {
return HelperActivityBase.createBaseIntent(context, KickoffActivity.class, flowParams);
}

@Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mKickstarter = ViewModelProviders.of(this).get(SignInKickstarter.class);
mKickstarter.init(getFlowParams());
mKickstarter.getOperation().observe(this, new Observer<Resource<IdpResponse>>() {
@Override
public void onChanged(Resource<IdpResponse> resource) {
if (resource.getState() == State.LOADING) {
getDialogHolder().showLoadingDialog(R.string.fui_progress_dialog_loading);
return;
}
getDialogHolder().dismissDialog();

if (savedInstance == null || savedInstance.getBoolean(IS_WAITING_FOR_PLAY_SERVICES)) {
if (isOffline()) {
Log.d(TAG, "No network connection");
finish(RESULT_CANCELED, IdpResponse.getErrorIntent(
new FirebaseUiException(ErrorCodes.NO_NETWORK)));
return;
if (resource.getState() == State.SUCCESS) {
finish(RESULT_OK, resource.getValue().toIntent());
} else if (resource.getState() == State.FAILURE) {
Exception e = resource.getException();
if (!FlowUtils.handleError(KickoffActivity.this, e)) {
finish(RESULT_CANCELED, IdpResponse.getErrorIntent(e));
} else if (e instanceof UserCancellationException) {
finish(RESULT_CANCELED, null);
}
}
}
});

boolean isPlayServicesAvailable = PlayServicesHelper.makePlayServicesAvailable(
this,
RequestCodes.PLAY_SERVICES_CHECK,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish(RESULT_CANCELED, IdpResponse.getErrorIntent(
new FirebaseUiException(ErrorCodes.PLAY_SERVICES_UPDATE_CANCELLED)));
}
});
if (savedInstanceState == null || savedInstanceState.getBoolean(IS_WAITING_FOR_PLAY_SERVICES)) {
init();
}
}

if (isPlayServicesAvailable) {
start();
} else {
mIsWaitingForPlayServices = true;
}
private void init() {
if (isOffline()) {
finish(RESULT_CANCELED, IdpResponse.getErrorIntent(
new FirebaseUiException(ErrorCodes.NO_NETWORK)));
return;
}

boolean isPlayServicesAvailable = PlayServicesHelper.makePlayServicesAvailable(
this,
RequestCodes.PLAY_SERVICES_CHECK,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish(RESULT_CANCELED, IdpResponse.getErrorIntent(
new FirebaseUiException(ErrorCodes.PLAY_SERVICES_UPDATE_CANCELLED)));
}
});

if (isPlayServicesAvailable) {
mKickstarter.start();
} else {
mIsWaitingForPlayServices = true;
}
}

@Override
public void onSaveInstanceState(Bundle outState) {
// It doesn't matter what we put here, we just don't want outState to be empty
outState.putBoolean(ExtraConstants.HAS_EXISTING_INSTANCE, true);
outState.putBoolean(IS_WAITING_FOR_PLAY_SERVICES, mIsWaitingForPlayServices);
super.onSaveInstanceState(outState);
outState.putBoolean(IS_WAITING_FOR_PLAY_SERVICES, mIsWaitingForPlayServices);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RequestCodes.PLAY_SERVICES_CHECK) {
if (resultCode == RESULT_OK) {
start();
mKickstarter.start();
} else {
finish(RESULT_CANCELED, IdpResponse.getErrorIntent(
new FirebaseUiException(ErrorCodes.PLAY_SERVICES_UPDATE_CANCELLED)));
}
} else {
SignInDelegate delegate = SignInDelegate.getInstance(this);
if (delegate != null) delegate.onActivityResult(requestCode, resultCode, data);
mKickstarter.onActivityResult(requestCode, resultCode, data);
}
}

private void start() {
FlowParameters flowParams = getFlowParams();
SignInDelegate.delegate(this, flowParams);
}

/**
* Check if there is an active or soon-to-be-active network connection.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,6 @@ public static FlowParameters fromBundle(Bundle bundle) {
return bundle.getParcelable(ExtraConstants.EXTRA_FLOW_PARAMS);
}

/**
* Create a bundle containing this FlowParameters object as {@link
* ExtraConstants#EXTRA_FLOW_PARAMS}.
*/
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putParcelable(ExtraConstants.EXTRA_FLOW_PARAMS, this);
return bundle;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(appName);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package com.firebase.ui.auth.data.remote;

import android.app.Activity;
import android.app.Application;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import com.firebase.ui.auth.AuthUI;
import com.firebase.ui.auth.IdpResponse;
import com.firebase.ui.auth.data.model.IntentRequiredException;
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.data.model.UserCancellationException;
import com.firebase.ui.auth.ui.email.EmailActivity;
import com.firebase.ui.auth.ui.idp.AuthMethodPickerActivity;
import com.firebase.ui.auth.ui.idp.SingleSignInActivity;
import com.firebase.ui.auth.ui.phone.PhoneActivity;
import com.firebase.ui.auth.util.GoogleApiUtils;
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.credentials.Credential;
import com.google.android.gms.auth.api.credentials.CredentialRequest;
import com.google.android.gms.auth.api.credentials.CredentialRequestResponse;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.ResolvableApiException;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.EmailAuthProvider;
import com.google.firebase.auth.FacebookAuthProvider;
import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
import com.google.firebase.auth.FirebaseAuthInvalidUserException;
import com.google.firebase.auth.GoogleAuthProvider;
import com.google.firebase.auth.PhoneAuthProvider;
import com.google.firebase.auth.TwitterAuthProvider;

import java.util.ArrayList;
import java.util.List;

public class SignInKickstarter extends AuthViewModelBase<IdpResponse> {
public SignInKickstarter(Application application) {
super(application);
}

public void start() {
// Only support password credentials if email auth is enabled
boolean supportPasswords = ProviderUtils.getConfigFromIdps(
getArguments().providerInfo, EmailAuthProvider.PROVIDER_ID) != null;
List<String> accountTypes = getCredentialAccountTypes();

// If the request will be empty, avoid the step entirely
boolean willRequestCredentials = supportPasswords || accountTypes.size() > 0;

if (getArguments().enableCredentials && willRequestCredentials) {
setResult(Resource.<IdpResponse>forLoading());

GoogleApiUtils.getCredentialsClient(getApplication())
.request(new CredentialRequest.Builder()
.setPasswordLoginSupported(supportPasswords)
.setAccountTypes(accountTypes.toArray(new String[accountTypes.size()]))
.build())
.addOnCompleteListener(new OnCompleteListener<CredentialRequestResponse>() {
@Override
public void onComplete(@NonNull Task<CredentialRequestResponse> task) {
try {
handleCredential(
task.getResult(ApiException.class).getCredential());
} catch (ResolvableApiException e) {
if (e.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) {
setResult(Resource.<IdpResponse>forUsableFailure(
new PendingIntentRequiredException(
e.getResolution(), RequestCodes.CRED_HINT)));
} else {
startAuthMethodChoice();
}
} catch (ApiException e) {
startAuthMethodChoice();
}
}
});
} else {
startAuthMethodChoice();
}
}

private void startAuthMethodChoice() {
List<AuthUI.IdpConfig> idpConfigs = getArguments().providerInfo;

// If there is only one provider selected, launch the flow directly
if (idpConfigs.size() == 1) {
AuthUI.IdpConfig firstIdpConfig = idpConfigs.get(0);
String firstProvider = firstIdpConfig.getProviderId();
switch (firstProvider) {
case EmailAuthProvider.PROVIDER_ID:
setResult(Resource.<IdpResponse>forUsableFailure(new IntentRequiredException(
EmailActivity.createIntent(getApplication(), getArguments()),
RequestCodes.EMAIL_FLOW)));
break;
case PhoneAuthProvider.PROVIDER_ID:
setResult(Resource.<IdpResponse>forUsableFailure(new IntentRequiredException(
PhoneActivity.createIntent(
getApplication(), getArguments(), firstIdpConfig.getParams()),
RequestCodes.PHONE_FLOW)));
break;
default:
redirectSignIn(firstProvider, null);
break;
}
} else {
setResult(Resource.<IdpResponse>forUsableFailure(new IntentRequiredException(
AuthMethodPickerActivity.createIntent(getApplication(), getArguments()),
RequestCodes.AUTH_PICKER_FLOW)));
}
}

private void redirectSignIn(String provider, String email) {
switch (provider) {
case EmailAuthProvider.PROVIDER_ID:
setResult(Resource.<IdpResponse>forUsableFailure(new IntentRequiredException(
EmailActivity.createIntent(getApplication(), getArguments(), email),
RequestCodes.EMAIL_FLOW)));
break;
case GoogleAuthProvider.PROVIDER_ID:
case FacebookAuthProvider.PROVIDER_ID:
case TwitterAuthProvider.PROVIDER_ID:
setResult(Resource.<IdpResponse>forUsableFailure(new IntentRequiredException(
SingleSignInActivity.createIntent(
getApplication(),
getArguments(),
new User.Builder(provider, email).build()),
RequestCodes.PROVIDER_FLOW)));
break;
default:
startAuthMethodChoice();
}
}

private List<String> getCredentialAccountTypes() {
List<String> accounts = new ArrayList<>();
for (AuthUI.IdpConfig idpConfig : getArguments().providerInfo) {
@AuthUI.SupportedProvider String providerId = idpConfig.getProviderId();
if (providerId.equals(GoogleAuthProvider.PROVIDER_ID)) {
accounts.add(ProviderUtils.providerIdToAccountType(providerId));
}
}
return accounts;
}

public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
switch (requestCode) {
case RequestCodes.CRED_HINT:
if (resultCode == Activity.RESULT_OK) {
handleCredential((Credential) data.getParcelableExtra(Credential.EXTRA_KEY));
} else {
startAuthMethodChoice();
}
break;
case RequestCodes.AUTH_PICKER_FLOW:
case RequestCodes.EMAIL_FLOW:
case RequestCodes.PHONE_FLOW:
case RequestCodes.PROVIDER_FLOW:
IdpResponse response = IdpResponse.fromResultIntent(data);
if (response == null) {
setResult(Resource.<IdpResponse>forFailure(new UserCancellationException()));
} else if (response.isSuccessful()) {
setResult(Resource.forSuccess(response));
} else {
setResult(Resource.<IdpResponse>forFailure(response.getError()));
}
}
}

private void handleCredential(final Credential credential) {
String id = credential.getId();
String password = credential.getPassword();
if (TextUtils.isEmpty(password)) {
String identity = credential.getAccountType();
if (identity == null) {
startAuthMethodChoice();
} else {
redirectSignIn(
ProviderUtils.accountTypeToProviderId(credential.getAccountType()), id);
}
} else {
final IdpResponse response = new IdpResponse.Builder(
new User.Builder(EmailAuthProvider.PROVIDER_ID, id).build()).build();

getAuth().signInWithEmailAndPassword(id, password)
.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
@Override
public void onSuccess(AuthResult result) {
setResult(Resource.forSuccess(response));
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
if (e instanceof FirebaseAuthInvalidUserException
|| e instanceof FirebaseAuthInvalidCredentialsException) {
// In this case the credential saved in SmartLock was not
// a valid credential, we should delete it from SmartLock
// before continuing.
GoogleApiUtils.getCredentialsClient(getApplication())
.delete(credential);
}
startAuthMethodChoice();
}
});
}
}
}
Loading