Smart Lock, full name Smart Lock for Passwords, is a Google API, announced at I/O 2015. A very simple description of it is an API that enables users signup and login to Android apps, without needing to enter a username/password combination! Yes, you read that right. With Smart Lock, app developers can design their applications to store (and sync across devices) usernames and passwords and/or federated login tokens. Our very own Edgar Cervantes has an excellent article on syncing passwords using Smart Lock.
One of the most exciting features of Smart Lock is that users are not limited to using Google accounts. Smart Lock supports username/password, Google, Twitter, Facebook, LinkedIn, Microsoft and even bespoke account types.
Great news so far. However, this article is not about discussing the pros and cons of Smart Lock, neither is it a press release. We intend to develop an application that implements Smart Lock, so, roll up your sleeves.
Preparing to code
Before you fire up your Android Studio, pause and study the image below. This diagram describes a typical (recommended) sign in flow for apps that utilize Smart Lock. While this is the recommended flow, your particular use case might be different, resulting in a completely different sign in chart. In that case, you might want to have a re-look at your design.

Some benefits of the above sign in method include:
- Existing users with a single saved credential are immediately signed in when they open the app, and will proceed directly to the default signed in view.
- For users with multiple credentials (or that have disabled automatic sign in), there is just a single dialog, before they are signed in to any one of their accounts.
- Users with no saved credentials can either select their username/email from the presented dialog and then be directed to either the sign up or sign in page, with the username and/or email already filled for them.
- Users that explicitly sign out are not automatically signed back in when the app is relaunched
Requirements
The requirements for developing and running an app with Smart Lock include
- An Android device running Android 2.3 (Gingerbread) or newer, OR, an emulator with an AVD running Google APIs platform based on Android 4.2.2 or newer, and has Google Play Services 9.0.2 or newer.
- Your project must be compiled for Android 2.3 (Gingerbread) or newer (Yes, I know, who still runs Froyo and earlier devices?).
Your project must have the Google Play Services SDK configured. To do this, follow the following steps:
- Open the SDK Manager (In Android Studio, select Tools – Android – SDK Manager).
- Select SDK Tools tab, check the Google Play services box, and Apply the changes.
- Add the line below to your applications build.gradle file (Note that 9.2.0 was the most recent version when this article was published):
dependencies {
// ...
compile 'com.google.android.gms:play-services-auth:9.2.0'
}
Demo app overview
The first thing we want to do is integrate some form of authentication in our app. Our demo app for this article, available in its entirety on github, has a LoginActivity, through which the user enters a username/password combination, and a ContentActivity, that contains a single Logout button. Finally, we have a Util class that simulates credential validation.
Our app will have the following features:
- Retrieve user sign in credentials, if it exists and login user automatically if there is exactly one user credential.Save user credentials when a user signs in (ideally also when the user signs up).
- Delete stored credentials when they have been changed outside a Smart Lock context
- Prevent user from automatically signing in if he explicitly signs out from the app
- Share a user’s credentials between your app and your website
Setup GoogleApiClient
In both LoginActivity and ContentActivity, we create a GoogleApiClient object instance. We build this GoogleApiClient object using the GoogleApiClient.Builder class. Both Activity’s implement GoogleApiClient.ConnectionCallbacks and GoogleApiClient.OnConnectionFailedListener, so that we can handle both successful and failed connection attempts.
public class LoginActivity extends AppCompatActivity implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private GoogleApiClient mGoogleApiClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.enableAutoManage(this, 0, this)
.addApi(Auth.CREDENTIALS_API)
.build();
}
@Override
public void onConnected(Bundle bundle) {
Log.d(TAG, "onConnected");
}
@Override
public void onConnectionSuspended(int cause) {
Log.d(TAG, "onConnectionSuspended: " + cause);
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.d(TAG, "onConnectionFailed: " + connectionResult);
}
}
We add the LoginActivity to the GoogleApiClient.Builder class’ callbacks with addConnectionCallbacks, to get a notification when the client has successfully connected. The enableAutoManage method expects a FragmentActivity, a client id (which we set to 0), and an implementation of GoogleApiClient.OnConnectionFailedListener, to get notifications for failed connection attempts.
Retrieve user credentials
Retrieved Credential
To retrieve a user’s saved credentials, we need to create a CredentialRequest object. With this object, we specify what types of credentials we are interested in retrieving.
In the code snippet below, our CredentialRequest object is built with setPasswordLoginSupported(true), to support password based login, and setAccountTypes(IdentityProviders.GOOGLE, IdentityProviders.TWITTER), to support both Google and Twitter sign-in. Other defined IdentityProviders include FACEBOOK, MICROSOFT and LINKEDIN. The IdentityProviders types are simply strings, and you are free to define your own IdentityProvider string.
We implement a requestCredentials() method. To attempt a user login immediately the app starts, call the requestCredentials method from the onConnected() method. This way, immediately the GoogleApiCLient alerts your app that it has established a connection, your app begins searching for available user credentials.
private void requestCredentials() {
mSignInButton.setEnabled(false);
mIsRequesting = true;
CredentialRequest request = new CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.setAccountTypes(IdentityProviders.GOOGLE, IdentityProviders.TWITTER)
.build();
After building the CredentialRequest object, we make a request using Auth.CredentialsApi.request. To handle success or failure result, we set a ResultCallback. If we get a single credential in response, we immediately process the credentials and sign in. If the user has multiple credentials, the status code returned is CommonStatusCodes.RESOLUTION_REQUIRED.
Auth.CredentialsApi.request(mGoogleApiClient, request).setResultCallback(
new ResultCallback() {
@Override
public void onResult(CredentialRequestResult credentialRequestResult) {
Status status = credentialRequestResult.getStatus();
if (credentialRequestResult.getStatus().isSuccess()) {
Credential credential = credentialRequestResult.getCredential();
processRetrievedCredential(credential);
} else if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) {
resolveResult(status, RC_READ);
} else if (status.getStatusCode() == CommonStatusCodes.SIGN_IN_REQUIRED) {
Log.d(TAG, "Sign in required");
mSignInButton.setEnabled(true);
} else {
Log.w(TAG, "Unrecognized status code: " + status.getStatusCode());
mSignInButton.setEnabled(true);
}
}
}
);
}
From the above snippet, if we find only one credential (getStatus().isSuccess()), we get the credentials and call processRetrieveCredentials (which is where we connect to the server with the credentials and attempt to login the user). If we get a RESOLUTION_REQUIRED, there is most likely multiple credentials that match, so we should call resolveResult to ask the user to select which of the credentials they want to login with.
Login with retrieved credentials
The processRetrievedCredential() method, for the demo app, is pretty straightforward. We check if the retrieved credential is a username/password or if it is a Google Sign-In. For username/password, we attempt to login the user with the combination, while for Google Sign-In, we automatically sign in the user with the found credentials. Completing the sign in process with a Google account is left as an exercise for readers.
NOTE in the code below, we call deleteCredential when we discover the credentials are invalid (as may be the case if the user changes his/her password).
private void processRetrievedCredential(Credential credential) {
String accountType = credential.getAccountType();
if(accountType == null) {
if (Util.isValidCredential(credential)) {
goToContent();
} else {
Log.d(TAG, "Retrieved credential invalid, so delete retrieved" +
" credential.");
Toast.makeText(this, "Retrieved credentials are invalid, so will be deleted.", Toast.LENGTH_LONG).show();
deleteCredential(credential);
requestCredentials();
mSignInButton.setEnabled(false);
}
}
else if (accountType.equals(IdentityProviders.GOOGLE)) {
GoogleSignInOptions gso =
new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.build();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this, this)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.setAccountName(credential.getId())
.build();
OptionalPendingResult opr =
Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
//...
}
}
Choose from multiple saved credentials
The resolveResult method checks the returned status code. Where there are multiple credentials and/or the user has explicitly signed out, we present a dialog with the available credentials by calling the startResolutionForResult() method.
private void resolveResult(Status status, int requestCode) {
if (mIsResolving) {
Log.w(TAG, "resolveResult: already resolving.");
return;
}
Log.d(TAG, "Resolving: " + status);
if (status.hasResolution()) {
Log.d(TAG, "STATUS: RESOLVING");
try {
status.startResolutionForResult(this, requestCode);
mIsResolving = true;
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "STATUS: Failed to send resolution.", e);
}
} else {
goToContent();
}
}
The result from startResolutionForResult() will be parsed back to the calling Activity (in this case, the LoginActivity) through the onActivityResult method.
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "onActivityResult:" + requestCode + ":" + resultCode + ":" + data);
if (requestCode == RC_READ) {
if (resultCode == RESULT_OK) {
Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
processRetrievedCredential(credential);
} else {
Log.e(TAG, "Credential Read: NOT OK");
setSignInEnabled(true);
}
} else if (requestCode == RC_SAVE) {
Log.d(TAG, "Result code: " + resultCode);
if (resultCode == RESULT_OK) {
Log.d(TAG, "Credential Save: OK");
} else {
Log.e(TAG, "Credential Save Failed");
}
goToContent();
}
mIsResolving = false;
}
At this point, your app would fetch user credentials from the Smart Lock password repository, if the user allows it. Unfortunately, your app does not save credentials yet, so, let’s remedy that.
Saving user credentials

To save a user’s credentials (when your server returns a successful login, or sign up is complete), we call Auth.CredentialsApi.save()
protected void saveCredential(Credential credential) {
Auth.CredentialsApi.save(mGoogleApiClient,
credential).setResultCallback(new ResultCallback() {
@Override
public void onResult(Status status) {
if (status.isSuccess()) {
Log.d(TAG, "Credential saved");
goToContent();
} else {
Log.d(TAG, "Attempt to save credential failed " +
status.getStatusMessage() + " " +
status.getStatusCode());
resolveResult(status, RC_SAVE);
}
}
});
}
Delete user credentials
Deleting credentials is also pretty easy. Simply call Auth.CredentialsApi.delete to delete the credential.
private void deleteCredential(Credential credential) {
Auth.CredentialsApi.delete(mGoogleApiClient,
credential).setResultCallback(new ResultCallback() {
@Override
public void onResult(Status status) {
if (status.isSuccess()) {
Log.d(TAG, "Credential successfully deleted.");
} else {
Log.d(TAG, "Credential not deleted successfully.");
}
}
});
}
Now, we have an app that can automatically sign users in with Smart Lock, can save and can delete a user’s credentials. However, at the moment, if a user explicitly logs out of our app, the app will automatically sign him in on restart. This is highly undesirable.
Prevent automatic sign-in
Preventing automatic sign in should be done whenever the user explicitly logs out from the app. They might want to sign in with other credentials. In the sample app, on successful sign in, the user is taken to ContentActivity. As discussed above, ContentActivity also has a GoogleApiClient object, and implements both ConnectionCallbacks and OnConnectionFailedListener interfaces.
When the user clicks on the logout button, we call Auth.CredentialsApi.disableAutoSignIn() method.
logoutButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Auth.CredentialsApi.disableAutoSignIn(mGoogleApiClient);
Intent intent = new Intent(v.getContext(), LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
}
});
Share login between your website and apps
For some true magic, users who have registered on your website can be automatically signed in when they download and run your app. Achieving this is simply a matter of placing the right configuration file in both your website and apps. This file, a Digital Asset Links file, must be placed on your website, and a link added to your app’s manifest. The only caveat is that your website’s sign in domain must be accessed through HTTPS.
To learn more and view sample Digital Asset Links, visit the Google guide
Happy coding
As always, the complete source for the demo app used for this article is available on github for use or misuse by all. You can also get up to speed by following Google’s hands-on Smart Lock codelabs.
Source: ANDRIOD AUTHORITY