package pub.devrel.easygoogle.gac; import android.app.Activity; import android.content.Intent; import android.support.annotation.NonNull; import android.util.Log; import com.google.android.gms.auth.api.Auth; import com.google.android.gms.auth.api.credentials.Credential; import com.google.android.gms.auth.api.credentials.CredentialPickerConfig; import com.google.android.gms.auth.api.credentials.CredentialRequest; import com.google.android.gms.auth.api.credentials.CredentialRequestResult; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.CommonStatusCodes; import com.google.android.gms.common.api.ResolvingResultCallbacks; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Scope; import com.google.android.gms.common.api.Status; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * Interface to the SmartLock for Passwords API which can be used to save and retrieve * id and password combinations on behalf of the user. SmartLock allows your users to * enter their password once in Android or Chrome and be automatically signed in across * all of their devices. For more information, visit: * https://developers.google.com/identity/smartlock-passwords/android */ public class SmartLock extends GacModule<SmartLock.SmartLockListener> { /** * Listener to be notified of asynchronous SmartLock events, like loading Credentials. */ public interface SmartLockListener { /** * A {@link Credential} has been successfully retrieved and the application * should validate the id/password and attempt to sign the user in. * @param credential the Credential chosen by the user. */ void onCredentialRetrieved(Credential credential); /** * There are Credentials available to load, but the app must display a picker * dialog to allow the user to choose. See {@link #showCredentialPicker()}. */ void onShouldShowCredentialPicker(); /** * There are no Credentials available to load, or the load operation failed or was * canceled by the user. */ void onCredentialRetrievalFailed(); } private static final String TAG = "SmartLock"; private static final int RC_READ = 9016; private static final int RC_SAVE = 9017; protected SmartLock() {} @Override public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == RC_READ) { if (resultCode == Activity.RESULT_OK && data != null) { Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY); getListener().onCredentialRetrieved(credential); } else { getListener().onCredentialRetrievalFailed(); } return true; } if (requestCode == RC_SAVE) { if (resultCode == Activity.RESULT_OK) { Log.d(TAG, "RC_SAVE: Ok"); } else { Log.d(TAG, "RC_SAVE: Failure"); } return true; } return false; } @Override public List<Api> getApis() { return Arrays.asList(new Api[]{Auth.CREDENTIALS_API}); } @Override public List<Scope> getScopes() { return Collections.emptyList(); } /** * Begin the process of retrieving a {@link Credential} for the device user. This can have * a few different results: * 1) If the user has auto sign-in enabled and exactly one previously saved credential, * {@link SmartLockListener#onCredentialRetrieved(Credential)} will be called and * you can sign the user in immediately. * 2) If the user has multiple saved credentials or one saved credential and has disabled * auto sign-in, you will get the callback {@link SmartLockListener#onShouldShowCredentialPicker()} * at which point you can choose to show the picker dialog to continue. * 3) If the user has no saved credentials or cancels the operation, you will receive the * {@link SmartLockListener#onCredentialRetrievalFailed()} callback. */ public void getCredentials() { CredentialRequest request = buildCredentialRequest(); Auth.CredentialsApi.request(getFragment().getGoogleApiClient(), request) .setResultCallback(new ResultCallback<CredentialRequestResult>() { @Override public void onResult(CredentialRequestResult result) { if (result.getStatus().isSuccess()) { // Single credential, auto sign-in Credential credential = result.getCredential(); getListener().onCredentialRetrieved(credential); } else if (result.getStatus().hasResolution() && result.getStatus().getStatusCode() != CommonStatusCodes.SIGN_IN_REQUIRED) { // Multiple credentials or auto-sign in disabled. If the status // code is SIGN_IN_REQUIRED then it is a hint credential, which we // do not want at this point. getListener().onShouldShowCredentialPicker(); } else { // Could not retrieve credentials getListener().onCredentialRetrievalFailed(); } } }); } /** * Show the dialog allowing the user to choose a Credential. This method shoud only be called * after you receive the {@link SmartLockListener#onShouldShowCredentialPicker()} callback. */ public void showCredentialPicker() { CredentialRequest request = buildCredentialRequest(); Activity activity = getFragment().getActivity(); int maskedCode = getFragment().maskRequestCode(RC_READ); Auth.CredentialsApi.request(getFragment().getGoogleApiClient(), request) .setResultCallback(new ResolvingResultCallbacks<CredentialRequestResult>(activity, maskedCode) { @Override public void onSuccess(CredentialRequestResult result) { getListener().onCredentialRetrieved(result.getCredential()); } @Override public void onUnresolvableFailure(Status status) { Log.e(TAG, "showCredentialPicker:onUnresolvableFailure:" + status); } }); } /** * Call this method if the user signs out of your application to disable auto sign-in on the * next run. This prevents users from getting stuck in a loop between sign-out and auto sign-in. */ public void signOut() { Auth.CredentialsApi.disableAutoSignIn(getFragment().getGoogleApiClient()) .setResultCallback(new ResultCallback<Status>() { @Override public void onResult(Status status) { Log.d(TAG, "signOut:onResult:" + status); } }); } /** * Save a new Credential to the user's SmartLock. This will be retrievable by all instances * of your application across devices so the user can avoid typing in his/her username and * password in the future. * @param id the id for the Credential, usually a username or email address. * @param password the password for the Credential. */ public void save(@NonNull String id, String password) { Credential credential = new Credential.Builder(id) .setPassword(password) .build(); // TODO(samstern): Should I notify the calling application on the success/failure of this // operation. If so, also need to do it in handleActivityResult. Activity activity = getFragment().getActivity(); int maskedCode = getFragment().maskRequestCode(RC_SAVE); Auth.CredentialsApi.save(getFragment().getGoogleApiClient(), credential) .setResultCallback(new ResolvingResultCallbacks<Status>(activity, maskedCode) { @Override public void onSuccess(Status status) { Log.d(TAG, "save:onSuccess:" + status); } @Override public void onUnresolvableFailure(Status status) { Log.d(TAG, "save:onUnresolvableFailure:" + status); } }); } /** * Delete a saved Credential object from the SmartLock store. * @param credential the Credential to delete. */ public void delete(Credential credential) { Auth.CredentialsApi.delete(getFragment().getGoogleApiClient(), credential) .setResultCallback(new ResultCallback<Status>() { @Override public void onResult(Status status) { Log.d(TAG, "delete:onResult:" + status); } }); } private CredentialRequest buildCredentialRequest() { return new CredentialRequest.Builder() .setCredentialPickerConfig(new CredentialPickerConfig.Builder() .setShowAddAccountButton(false) .setShowCancelButton(true) .setForNewAccount(false) .build()) .setPasswordLoginSupported(true) .build(); } }