In this codelab, you'll learn how to use Smart Lock to provide your users with a simple onboarding process. Presenting a user with a login screen as few times as possible, likely only once.

What you'll learn

What you'll need

You can either download all the sample code to your computer...

Download Zip

...or clone the GitHub repository from the command line.

$ git clone https://github.com/googlecodelabs/android-smart-lock.git

Now you're ready to build on top of the starter project to add Smart Lock to it.

  1. Select the android-smart-lock directory from your sample code download (File > Import Project... > android-smart-lock).
  2. Click the Gradle sync button.
  3. Click the Run button.

You should see the SmartLockCodelab home screen appear after a few seconds.

You can sign in with a valid username and password, "username1" and "password1" will work. This takes you to the content screen. However if you uninstall the application and reinstall it, you will have to sign in again.

The steps below will allow us to add the ability to save credentials and retrieve them when needed making the sign in flow for the user seamless.

First we need to add the Auth dependency that would provide the classes necessary for Smart Lock. Include the com.google.android.gms:play-services-auth:9.0.0 dependency in your dependency block.

build.gradle

dependencies {
   compile fileTree(dir: 'libs', include: ['*.jar'])
   compile 'com.android.support:design:23.0.1'
   compile 'com.google.android.gms:play-services-auth:9.0.0'
   testCompile 'junit:junit:4.12'
}

To save valid credentials we will:

Include required fields.

Add the GoogleApiClient instance variable in MainActivity.java.

MainActivity.java

private GoogleApiClient mGoogleApiClient;

Build GoogleApiClient.

Build GoogleApiClient in the onCreate method of MainActivity.java. Add the code below just after setContentView(...).

MainActivity.java

mGoogleApiClient = new GoogleApiClient.Builder(this)
        .addConnectionCallbacks(this)
        .enableAutoManage(this, 0, this)
        .addApi(Auth.CREDENTIALS_API)
        .build();

MainActivity must implement GoogleApiClient.ConnectionCallbacks and GoogleApiClient.OnConnectionFailedListener. The class declaration should now start like this.

MainActivity.java

public class MainActivity extends AppCompatActivity implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

Implementing these interfaces requires the implementation of onConnected(...), onConnectionSuspended(...) and onConnectionFailed(...). Add the methods below to satisfy the requirements of the implemented interfaces.

MainActivity.java

@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);
}

Initiate save of valid credential.

When a valid credential is found initiate a call to save it. Replace the current if/else block in the Sign In button click listener. Check the credential now with the code below in SignInFragment.

SignInFragment.java

Credential credential = new Credential.Builder(username)
        .setPassword(password)
        .build();
if (CodelabUtil.isValidCredential(credential)) {
    ((MainActivity) getActivity()).saveCredential(credential);
} else {
    Log.d(TAG, "Credentials are invalid. Username or password are " +
            "incorrect.");
    Toast.makeText(view.getContext(), R.string.invalid_creds_toast_msg,
            Toast.LENGTH_SHORT).show();
    setSignEnabled(true);
}

Update the CodelabUtil class with a method that is able to validate a Credential object. Add the following method to CodelabUtil.java.

CodelabUtil.java

public static boolean isValidCredential(Credential credential) {
    String username = credential.getId();
    String password = credential.getPassword();
    return isValidCredential(username, password);
}

Also add the required saveCredential method to the MainActivity.java class.

MainActivity.java

protected void saveCredential(Credential credential) {
   // Credential is valid so save it.
   Auth.CredentialsApi.save(mGoogleApiClient, 
           credential).setResultCallback(new ResultCallback<Status>() {
       @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);
           }
       }
   });
}

Start resolution if credential is new

If the credential is new the user must give permission for the credential to be saved. Add the following method to MainActivity.java.

MainActivity.java

private void resolveResult(Status status, int requestCode) {
    // We don't want to fire multiple resolutions at once since that
    // can   result in stacked dialogs after rotation or another
    // similar event.
    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 {
        Log.e(TAG, "STATUS: FAIL");
        if (requestCode == RC_SAVE) {
            goToContent();
        }
    }
}

Respond to user's decision (save or not save)

Once the user has responded to the dialog, indicating whether or not they want to save the credential, the Activity must handle that response by overriding the onActivityResult method in SignInActivity.java. Add the method below to SignInActivity.java.

MainActivity.java

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    Log.d(TAG, "onActivityResult:" + requestCode + ":" + resultCode + ":" +
            data);
    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;
}

Now verify that when a valid credentials are saved.

  1. Click the Run button. You should see the Smart Lock Codelab App home screen appear after a few seconds.
  2. Enter a valid username and password and click the "Sign In" button.
  3. Verify that a dialog offering options to save the password is presented.
  4. Click the "Save Password" button.
  5. Verify that the user is taken to the content screen.

Once GoogleApiClient is connected you can request saved Smart Lock credentials. A good place to do it is in the onConnected callback.

Update the onConnected method in MainActivity to requestCredentials once connected.

MainActivity.java

@Override
public void onConnected(Bundle bundle) {
   Log.d(TAG, "onConnected");

   // Request Credentials once connected. If credentials are retrieved
   // the user will either be automatically signed in or will be
   // presented with credential options to be used by the application
   // for sign in.
   requestCredentials();
}

Add the requestCredentials method to MainActivity.

MainActivity.java

private void requestCredentials() {
      setSignInEnabled(false);
mIsRequesting = true;

CredentialRequest request = new CredentialRequest.Builder()
       .setSupportsPasswordLogin(true)
       .build();

Auth.CredentialsApi.request(mGoogleApiClient, request).setResultCallback(
       new ResultCallback<CredentialRequestResult>() {
           @Override
           public void onResult(CredentialRequestResult credentialRequestResult) {
               mIsRequesting = false;
               Status status = credentialRequestResult.getStatus();
               if (credentialRequestResult.getStatus().isSuccess()) {
                   // Successfully read the credential without any user interaction, this
                   // means there was only a single credential and the user has auto
                   // sign-in enabled.
                   Credential credential = credentialRequestResult.getCredential();
                   processRetrievedCredential(credential);
               } else if (status.getStatusCode() == CommonStatusCodes.SIGN_IN_REQUIRED) {
                   setFragment(null);
                   // This is most likely the case where the user does not currently
                   // have any saved credentials and thus needs to provide a username
                   // and password to sign in.
                   Log.d(TAG, "Sign in required");
                   setSignInEnabled(true);
               } else {
                   Log.w(TAG, "Unrecognized status code: " + status.getStatusCode());
                   setFragment(null);
                   setSignInEnabled(true);
               }
           }
       }
);

}

Add processRetrievedCredential method.

MainActivity.java

private void processRetrievedCredential(Credential credential) {
   if (CodelabUtil.isValidCredential(credential)) {
       goToContent();
   } else {
       // This is likely due to the credential being changed outside of
       // Smart Lock,
       // ie: away from Android or Chrome. The credential should be deleted
       // and the user allowed to enter a valid credential.
       Log.d(TAG, "Retrieved credential invalid, so delete retrieved" +
               " credential.");

   }
}

Now verify that when a valid credentials are saved.

  1. Click the Run button. You should see the Smart Lock Codelab App home screen appear after a few seconds.
  2. Verify that you are automatically signed in by requesting the single saved credential.
  3. Verify that a toast indicating username used for auto signin is displayed.

Auto Sign In occurs when there is exactly one saved credential for the user associated with the application. This allows the user to seamlessly sign into your application.

You may notice that every time you return to the SignIn screen you are automatically signed in again. If the user wants to use different credentials then how will they be able to enter new credentials if they are automatically signed in? You can disable auto sign in by calling disableAutoSignIn. You would generally want to use this when the user signs out of your application.

Add GoogleApiClient to ContentActivity.java.

ContentActivity.java

private GoogleApiClient mGoogleApiClient;

Build GoogleApiClient in onCreate of ContentActivity.java

ContentActivity.java

mGoogleApiClient = new GoogleApiClient.Builder(this)
       .addConnectionCallbacks(this)
       .enableAutoManage(this, 0, this)
       .addApi(Auth.CREDENTIALS_API)
       .build();

Add the required interfaces.

ContentActivity.java

public class ContentActivity extends AppCompatActivity implements 
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

Implement methods required by implemented interfaces.

ContentActivity.java

@Override
public void onConnected(Bundle bundle) {
    Log.d(TAG, "GoogleApiClient connected");
}

@Override
public void onConnectionSuspended(int cause) {
    Log.d(TAG, "GoogleApiClient is suspended with cause code: " + cause);
}

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
    Log.d(TAG, "GoogleApiClient failed to connect: " + connectionResult);
}

Update the signout button click listener in the ContentActivity so it looks like the one below.

ContentActivity.java

logoutButton.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       Auth.CredentialsApi.disableAutoSignIn(mGoogleApiClient);
                Intent intent = new Intent(v.getContext(), MainActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                startActivity(intent);
                finish();

   }
});

Once you disable auto sign in, when you request credentials in the SignInActivity you will be presented with a dialog of all the previously saved credentials, one at this point. The user will have the opportunity to select one to be used for login.

Now verify that when you sign out you are not automatically signed in again.

  1. Click the Run button. You should see the Smart Lock Codelab App home screen appear after a few seconds.
  2. Verify that you are automatically signed in by requesting the single saved credential.
  3. Verify that a toast indicating username used for auto signin is displayed.
  4. Hit Sign Out button.
  5. Verify that you are not automatically signed in.
  6. Also note log statement indicating unrecognized status code.

If there are multiple credentials saved for the same application associated with the same Google Account then they will be presented to the user to select which credential to be used for sign in.

When on the Sign In screen and presented with a single saved credential select CANCEL then enter another valid credential. For this codelab you can use username: username2 password: password2. Save the valid credential then when on the content screen hit SIGN OUT.

To handle multiple saved credentials check for additional request credential callback statuses. Update the else portion of the credential request callback.

MainActivity.java

private void requestCredentials() {
   setSignInEnabled(false);
   mIsRequesting = true;

   CredentialRequest request = new CredentialRequest.Builder()
           .setSupportsPasswordLogin(true)
           .build();

   Auth.CredentialsApi.request(mGoogleApiClient, request).setResultCallback(
           new ResultCallback<CredentialRequestResult>() {
               @Override
               public void onResult(CredentialRequestResult credentialRequestResult) {
                   mIsRequesting = false;
                   Status status = credentialRequestResult.getStatus();
                   if (credentialRequestResult.getStatus().isSuccess()) {
                       // Successfully read the credential without any user interaction, this
                       // means there was only a single credential and the user has auto
                       // sign-in enabled.
                       Credential credential = credentialRequestResult.getCredential();
                       processRetrievedCredential(credential);
                   } else if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) {
                       setFragment(null);
                       // This is most likely the case where the user has multiple saved
                       // credentials and needs to pick one.
                       resolveResult(status, RC_READ);
                   } else if (status.getStatusCode() == CommonStatusCodes.SIGN_IN_REQUIRED) {
                       setFragment(null);
                       // This is most likely the case where the user does not currently
                       // have any saved credentials and thus needs to provide a username
                       // and password to sign in.
                       Log.d(TAG, "Sign in required");
                       setSignInEnabled(true);
                   } else {
                       Log.w(TAG, "Unrecognized status code: " + status.getStatusCode());
                       setFragment(null);
                       setSignInEnabled(true);
                   }
               }
           }
   );
}

Update resolveResult method in MainActivity.java so it handles the status RESOLUTION_REQUIRED.

MainActivity.java

private void resolveResult(Status status, int requestCode) {
   // We don't want to fire multiple resolutions at once since that can result
   // in stacked dialogs after rotation or another similar event.
   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();
   }
}

Handle reading in onActivityResult in MainActivity.java.

MainActivity.java

@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;
    }

Try handling multiple credentials.

Now verify that when you sign out you are not automatically signed in again.

  1. Click the Run button. You should see the Smart Lock Codelab App home screen appear after a few seconds.
  2. Confirm that you are presented with two sets of credentials, either of which would take you to the content screen.

If your users change their credentials on a platform that does not support Smart Lock, then the next time that credential is retrieved it will not be valid. In that case you should delete that credential and allow the user to sign in again with valid credentials saving the newly verified credentials.

Update the processRetrievedCredential method to delete a retrieved credential if it is not valid.

MainActivity.java

private void processRetrievedCredential(Credential credential) {
   if (CodelabUtil.isValidCredential(credential)) {
       goToContent();
   } else {
       // This is likely due to the credential being changed outside of
       // Smart Lock,
       // ie: away from Android or Chrome. The credential should be deleted
       // and the user allowed to enter a valid credential.
       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();
        setSignInEnabled(false);
   }
}

Add the deletCredential method to SignInActivity.java.

MainActivity.java

private void deleteCredential(Credential credential) {
   Auth.CredentialsApi.delete(mGoogleApiClient, 
           credential).setResultCallback(new ResultCallback<Status>() {
       @Override
       public void onResult(Status status) {
           if (status.isSuccess()) {
               Log.d(TAG, "Credential successfully deleted.");
           } else {
               // This may be due to the credential not existing, possibly
               // already deleted via another device/app.
               Log.d(TAG, "Credential not deleted successfully.");
           }
       }
   });
}

Now verify that if an invalid credential is retrieved that it is deleted.

  1. Click the Run button. You should see the Smart Lock App home screen appear after a few seconds.
  2. Sign in with the presented credential.
  3. Once on the content screen the CHANGE CREDS button.
  4. Adjust the first username and password pair. Set the password to password5.
  5. Press back to return to the content screen and SIGN OUT to return to the login screen.
  6. Select the retrieved credential again.
  7. Confirm that the user is not taken to the content screen and the log should contain a message indicating that the credential has been deleted.
  8. Now use the new valid credentials username: username1 and password: password5 to sign in.
  9. Confirm that the user is taken to the content screen.

Your app is now ready to use Smart Lock to provide seamless sign in for your users.

What we've covered

Next Step

Learn More