In this tutorial you will learn about Credentials API usage via simple step by step examples.
What is Credentials API
It is set of facilities that allows us retrieve and save app login credentials.
Through the Smart Lock for Passwords and Connected Accounts API facilitates, you can save and retrieve credentials for your app and associated site. They also help to expedite account creation through credential hints. Integrating with this API will simplify the sign-in experience for users moving between devices where they are signed in to their Google account.
For example, a user may have an account on a website and have saved the credential for this account in Chrome. A new Android app is released for this service to provide a better mobile experience, and users are encouraged to download the app when they visit the site from mobile Chrome. This API allows the app to retrieve the saved password that was used in Chrome, providing a seamless login experience for the user without the need to manually enter credentials.
You can read more here.
Let us look at some simple examples:
Example 1: Credentials API + Google Sign In API
This example teaches you how to use both the Credentials API (SmartLock for Passwords) and the Google Sign In API in the same application.
Step 1: Create Project
Start by creating an empty Android Studio
project.
Step 2: Dependencies
Go to your app/build.gradle
and add Google play services auth as follows:
implementation 'com.google.android.gms:play-services-auth:19.0.0'
Step 3: Design Layout
Go to your activity_main.xml
and design your layout as follows:
activity_main.xml
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
android:overScrollMode="always">
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/google_sign_in_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardCornerRadius="4dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/text_google_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/signed_out" />
<com.google.android.gms.common.SignInButton
android:id="@+id/button_google_sign_in"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/button_google_sign_out"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@string/sign_out" />
<Button
android:id="@+id/button_google_revoke"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@string/revoke_access" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/email_sign_in_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardCornerRadius="4dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/text_email_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/signed_out" />
<Button
android:id="@+id/button_email_sign_in"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sign_in_email_cta" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
android:text="Save New Credential" />
<EditText
android:id="@+id/edit_text_email"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="Email"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/edit_text_password"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword" />
<Button
android:id="@+id/button_email_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Save" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</ScrollView>
Step 4: Write Code
Go your MainActivity.java
and add the following:
MainActivity.java
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
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.auth.api.credentials.Credentials;
import com.google.android.gms.auth.api.credentials.CredentialsClient;
import com.google.android.gms.auth.api.credentials.IdentityProviders;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.SignInButton;
import com.google.android.gms.common.api.ResolvableApiException;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
public class MainActivity extends AppCompatActivity implements
View.OnClickListener {
private static final String TAG = "MainActivity";
private static final String KEY_IS_RESOLVING = "is_resolving";
private static final String KEY_CREDENTIAL = "key_credential";
private static final String KEY_CREDENTIAL_TO_SAVE = "key_credential_to_save";
private static final int RC_SIGN_IN = 1;
private static final int RC_CREDENTIALS_READ = 2;
private static final int RC_CREDENTIALS_SAVE = 3;
private CredentialsClient mCredentialsClient;
private GoogleSignInClient mSignInClient;
private ProgressDialog mProgressDialog;
private boolean mIsResolving = false;
private Credential mCredential;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
mIsResolving = savedInstanceState.getBoolean(KEY_IS_RESOLVING, false);
mCredential = savedInstanceState.getParcelable(KEY_CREDENTIAL);
}
// Build CredentialsClient and GoogleSignInClient, don't set account name
buildClients(null);
// Sign in button
SignInButton signInButton = (SignInButton) findViewById(R.id.button_google_sign_in);
signInButton.setSize(SignInButton.SIZE_WIDE);
signInButton.setOnClickListener(this);
// Other buttons
findViewById(R.id.button_email_sign_in).setOnClickListener(this);
findViewById(R.id.button_google_revoke).setOnClickListener(this);
findViewById(R.id.button_google_sign_out).setOnClickListener(this);
findViewById(R.id.button_email_save).setOnClickListener(this);
}
private void buildClients(String accountName) {
GoogleSignInOptions.Builder gsoBuilder = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail();
if (accountName != null) {
gsoBuilder.setAccountName(accountName);
}
mCredentialsClient = Credentials.getClient(this);
mSignInClient = GoogleSignIn.getClient(this, gsoBuilder.build());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_IS_RESOLVING, mIsResolving);
outState.putParcelable(KEY_CREDENTIAL, mCredential);
}
@Override
public void onStart() {
super.onStart();
if (!mIsResolving) {
requestCredentials(true /* shouldResolve */, false /* onlyPasswords */);
}
}
@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_SIGN_IN) {
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
handleGoogleSignIn(task);
} else if (requestCode == RC_CREDENTIALS_READ) {
mIsResolving = false;
if (resultCode == RESULT_OK) {
Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
handleCredential(credential);
}
} else if (requestCode == RC_CREDENTIALS_SAVE) {
mIsResolving = false;
if (resultCode == RESULT_OK) {
Toast.makeText(this, "Saved", Toast.LENGTH_SHORT).show();
} else {
Log.w(TAG, "Credential save failed.");
}
}
}
private void googleSilentSignIn() {
// Try silent sign-in with Google Sign In API
Task<GoogleSignInAccount> silentSignIn = mSignInClient.silentSignIn();
if (silentSignIn.isComplete() && silentSignIn.isSuccessful()) {
handleGoogleSignIn(silentSignIn);
return;
}
showProgress();
silentSignIn.addOnCompleteListener(new OnCompleteListener<GoogleSignInAccount>() {
@Override
public void onComplete(@NonNull Task<GoogleSignInAccount> task) {
hideProgress();
handleGoogleSignIn(task);
}
});
}
private void handleCredential(Credential credential) {
mCredential = credential;
Log.d(TAG, "handleCredential:" + credential.getAccountType() + ":" + credential.getId());
if (IdentityProviders.GOOGLE.equals(credential.getAccountType())) {
// Google account, rebuild GoogleApiClient to set account name and then try
buildClients(credential.getId());
googleSilentSignIn();
} else {
// Email/password account
String status = String.format("Signed in as %s", credential.getId());
((TextView) findViewById(R.id.text_email_status)).setText(status);
}
}
private void handleGoogleSignIn(Task<GoogleSignInAccount> completedTask) {
Log.d(TAG, "handleGoogleSignIn:" + completedTask);
boolean isSignedIn = (completedTask != null) && completedTask.isSuccessful();
if (isSignedIn) {
// Display signed-in UI
GoogleSignInAccount gsa = completedTask.getResult();
String status = String.format("Signed in as %s (%s)", gsa.getDisplayName(),
gsa.getEmail());
((TextView) findViewById(R.id.text_google_status)).setText(status);
// Save Google Sign In to SmartLock
Credential credential = new Credential.Builder(gsa.getEmail())
.setAccountType(IdentityProviders.GOOGLE)
.setName(gsa.getDisplayName())
.setProfilePictureUri(gsa.getPhotoUrl())
.build();
saveCredential(credential);
} else {
// Display signed-out UI
((TextView) findViewById(R.id.text_google_status)).setText(R.string.signed_out);
}
findViewById(R.id.button_google_sign_in).setEnabled(!isSignedIn);
findViewById(R.id.button_google_sign_out).setEnabled(isSignedIn);
findViewById(R.id.button_google_revoke).setEnabled(isSignedIn);
}
private void requestCredentials(final boolean shouldResolve, boolean onlyPasswords) {
CredentialRequest.Builder crBuilder = new CredentialRequest.Builder()
.setPasswordLoginSupported(true);
if (!onlyPasswords) {
crBuilder.setAccountTypes(IdentityProviders.GOOGLE);
}
showProgress();
mCredentialsClient.request(crBuilder.build()).addOnCompleteListener(
new OnCompleteListener<CredentialRequestResponse>() {
@Override
public void onComplete(@NonNull Task<CredentialRequestResponse> task) {
hideProgress();
if (task.isSuccessful()) {
// Auto sign-in success
handleCredential(task.getResult().getCredential());
return;
}
Exception e = task.getException();
if (e instanceof ResolvableApiException && shouldResolve) {
// Getting credential needs to show some UI, start resolution
ResolvableApiException rae = (ResolvableApiException) e;
resolveResult(rae, RC_CREDENTIALS_READ);
} else {
Log.w(TAG, "request: not handling exception", e);
}
}
});
}
private void resolveResult(ResolvableApiException rae, int requestCode) {
if (!mIsResolving) {
try {
rae.startResolutionForResult(MainActivity.this, requestCode);
mIsResolving = true;
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Failed to send Credentials intent.", e);
mIsResolving = false;
}
}
}
private void saveCredential(Credential credential) {
if (credential == null) {
Log.w(TAG, "Ignoring null credential.");
return;
}
mCredentialsClient.save(mCredential).addOnCompleteListener(
new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d(TAG, "save:SUCCESS");
return;
}
Exception e = task.getException();
if (e instanceof ResolvableApiException) {
// Saving the credential can sometimes require showing some UI
// to the user, which means we need to fire this resolution.
ResolvableApiException rae = (ResolvableApiException) e;
resolveResult(rae, RC_CREDENTIALS_SAVE);
} else {
Log.w(TAG, "save:FAILURE", e);
Toast.makeText(MainActivity.this,
"Unexpected error, see logs for detals",
Toast.LENGTH_SHORT).show();
}
}
});
}
private void onGoogleSignInClicked() {
Intent intent = mSignInClient.getSignInIntent();
startActivityForResult(intent, RC_SIGN_IN);
}
private void onGoogleRevokeClicked() {
if (mCredential != null) {
mCredentialsClient.delete(mCredential);
}
mSignInClient.revokeAccess().addOnCompleteListener(
new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
handleGoogleSignIn(null);
}
});
}
private void onGoogleSignOutClicked() {
mCredentialsClient.disableAutoSignIn();
mSignInClient.signOut().addOnCompleteListener(
new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
handleGoogleSignIn(null);
}
});
}
private void onEmailSignInClicked() {
requestCredentials(true, true);
}
private void onEmailSaveClicked() {
String email = ((EditText) findViewById(R.id.edit_text_email)).getText().toString();
String password = ((EditText) findViewById(R.id.edit_text_password)).getText().toString();
if (email.length() == 0|| password.length() == 0) {
Log.w(TAG, "Blank email or password, can't save Credential.");
Toast.makeText(this, "Email/Password must not be blank.", Toast.LENGTH_SHORT).show();
return;
}
Credential credential = new Credential.Builder(email)
.setPassword(password)
.build();
saveCredential(credential);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_google_sign_in:
onGoogleSignInClicked();
break;
case R.id.button_google_revoke:
onGoogleRevokeClicked();
break;
case R.id.button_google_sign_out:
onGoogleSignOutClicked();
break;
case R.id.button_email_sign_in:
onEmailSignInClicked();
break;
case R.id.button_email_save:
onEmailSaveClicked();
break;
}
}
private void showProgress() {
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setIndeterminate(true);
mProgressDialog.setMessage("Loading...");
}
mProgressDialog.show();
}
private void hideProgress() {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
}
}
Run
Copy the code or download it in the link below, build and run.
Reference
Here are the reference links:
Number | Link |
---|---|
1. | Download Example |
2. | Follow code author |
3. | Code: Apache 2.0 License |
Example 2: Save and Load Username/Password Credentials
This is an example for the Android Credentials API, which is part of Smartlock for Passwords on Android. This example will show how to save and load username/password credentials from the Credentials API.
Step 1: Create Project
Start by creating an empty Android Studio
project.
Step 2: Dependencies
Go to your app/build.gradle
and add Google Play Services Auth and Google API Client as follows:
implementation 'com.google.android.gms:play-services-auth:19.0.0'
implementation 'com.google.api-client:google-api-client:1.22.0'
Step 3: Add Internet Permission
Add internet permission in your app/src/main/AndroidManifest.xml
:
<uses-permission android:name="android.permission.INTERNET" />
Step 4: Design Layout
Design your MainActivity layout as follows:
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:indeterminate="true"
android:indeterminateOnly="true"
android:visibility="invisible"
tools:visibility="visible" />
</androidx.appcompat.widget.Toolbar>
<EditText
android:id="@+id/edit_text_email"
android:layout_width="@dimen/width_field"
android:layout_height="wrap_content"
android:layout_below="@+id/toolbar"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:gravity="center_horizontal"
android:hint="@string/hint_email"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/edit_text_password"
android:layout_width="@dimen/width_field"
android:layout_height="wrap_content"
android:layout_below="@+id/edit_text_email"
android:layout_centerHorizontal="true"
android:gravity="center_horizontal"
android:hint="@string/hint_password"
android:inputType="textPassword" />
<LinearLayout
android:id="@+id/layout_save_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/edit_text_password"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:orientation="horizontal">
<Button
android:id="@+id/button_save_credential"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:text="@string/action_save_credential" />
<Button
android:id="@+id/button_delete_loaded_credential"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@string/delete_credential" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_load"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/layout_save_delete"
android:layout_centerHorizontal="true"
android:orientation="horizontal">
<Button
android:id="@+id/button_load_credentials"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:enabled="true"
android:text="@string/load_credentials" />
<Button
android:id="@+id/button_load_hint"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:enabled="true"
android:text="@string/load_hint" />
</LinearLayout>
<LinearLayout
android:layout_below="@+id/layout_load"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_request_id_token"/>
<CheckBox
android:id="@+id/checkbox_request_idtoken"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"/>
</LinearLayout>
</RelativeLayout>
Step 5: Create A Mocker Server
Create a MockServer.java
file and add the following code:
MockServer.java
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
import com.google.android.gms.auth.api.credentials.IdentityProviders;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
/**
* <b>Mock</b> server class to demonstrate how to use the Google APIs Client Library for Java
* to verify an ID token obtained from a Credential.
*/
public class MockServer extends IntentService {
public static final String TAG = "MockServer";
public static final String EXTRA_IDTOKEN = "id_token";
private static final String PACKAGE_NAME = "com.google.example.credentialsbasic";
private static final String SHA512_HASH = "YOUR_SHA512_HASH";
private static final HttpTransport transport = new NetHttpTransport();
private static final JsonFactory jsonFactory = new JacksonFactory();
// Verifier that checks that the token has the proper issuer and audience
private static GoogleIdTokenVerifier verifier =
new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setIssuer(IdentityProviders.GOOGLE)
.setAudience(Arrays.asList(getAudienceString(SHA512_HASH, PACKAGE_NAME)))
.build();
public MockServer() {
super(TAG);
}
/**
* Verify an ID token and log the email address and verification status.
* @param idTokenString ID Token from a Credential.
*/
private static void verifyIdToken(String idTokenString) {
// Print the audience to the logs
logTokenAudience(idTokenString);
try {
// Verify ID Token
GoogleIdToken idToken = verifier.verify(idTokenString);
if (idToken == null) {
Log.w(TAG, "ID Token Verification Failed, check the README for instructions.");
return;
}
// Extract email address and verification
GoogleIdToken.Payload payload = idToken.getPayload();
Log.d(TAG, "IdToken:" + payload.toPrettyString());
Log.d(TAG, "IdToken:Email:" + payload.getEmail());
Log.d(TAG, "IdToken:EmailVerified:" + payload.getEmailVerified());
} catch (GeneralSecurityException e) {
Log.e(TAG, "verifyIdToken:GeneralSecurityException", e);
} catch (IOException e) {
Log.e(TAG, "verifyIdToken:IOException", e);
}
}
/**
* Print the audience of an unverified token string to the logs.
* @param idTokenString the ID Token string.
*/
public static void logTokenAudience(String idTokenString) {
try {
GoogleIdToken idToken = GoogleIdToken.parse(jsonFactory, idTokenString);
Log.d(TAG, "IDToken Audience:" + idToken.getPayload().getAudience());
} catch (IOException e) {
Log.e(TAG, "IDToken Audience: Could not parse ID Token", e);
}
}
/**
* Determine the audience of the ID token based on the sha512 hash and the package name.
* @param sha512Hash sha512 hash of the application, see README for instructions.
* @param packageName package name of the application,
*/
private static String getAudienceString(String sha512Hash, String packageName) {
String fmtString = "android://%s@%s";
return String.format(fmtString, sha512Hash, packageName);
}
@Override
protected void onHandleIntent(Intent intent) {
String idToken = intent.getStringExtra(EXTRA_IDTOKEN);
if (idToken != null) {
Log.d(TAG, "Processing ID Token:" + idToken);
verifyIdToken(idToken);
}
}
}
Step 6: MainActivity
Extend the AppCompatActivity and define constants as well as instance fields as follows:
public class MainActivity extends AppCompatActivity implements
View.OnClickListener {
private static final String TAG = MainActivity.class.getSimpleName();
private static final String KEY_IS_RESOLVING = "is_resolving";
private static final int RC_SAVE = 1;
private static final int RC_HINT = 2;
private static final int RC_READ = 3;
private EditText mEmailField;
private EditText mPasswordField;
private CredentialsClient mCredentialsClient;
private Credential mCurrentCredential;
private boolean mIsResolving = false;
Override the onCreate()
:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
Initialize our widgets:
```java
// Fields
mEmailField = findViewById(R.id.edit_text_email);
mPasswordField = findViewById(R.id.edit_text_password);
// Buttons
findViewById(R.id.button_save_credential).setOnClickListener(this);
findViewById(R.id.button_load_credentials).setOnClickListener(this);
findViewById(R.id.button_load_hint).setOnClickListener(this);
findViewById(R.id.button_delete_loaded_credential).setOnClickListener(this);</code></pre>
<p>Handle our Instance state:</p>
<pre><code class="language-java"> if (savedInstanceState != null) {
mIsResolving = savedInstanceState.getBoolean(KEY_IS_RESOLVING);
}</code></pre>
<p>Instantiate client for interacting with the credentials API. For this demo application we forcibly enable the SmartLock save dialog, which is sometimes disabled when it would conflict with the Android autofill API:</p>
<pre><code class="language-java"> CredentialsOptions options = new CredentialsOptions.Builder()
.forceEnableSaveDialog()
.build();
mCredentialsClient = Credentials.getClient(this, options);
}</code></pre>
<p>Override the onStart and attempt to auto-sign in:</p>
<pre><code class="language-java"> @Override
public void onStart() {
super.onStart();
// Attempt auto-sign in.
if (!mIsResolving) {
requestCredentials();
}
}</code></pre>
<p>Override onStaveinstance state and onActivity Result:</p>
<pre><code class="language-java"> @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_IS_RESOLVING, mIsResolving);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "onActivityResult:" + requestCode + ":" + resultCode + ":" + data);
hideProgress();
switch (requestCode) {
case RC_HINT:
// Drop into handling for RC_READ
case RC_READ:
if (resultCode == RESULT_OK) {
boolean isHint = (requestCode == RC_HINT);
Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
processRetrievedCredential(credential, isHint);
} else {
Log.e(TAG, "Credential Read: NOT OK");
showToast("Credential Read Failed");
}
mIsResolving = false;
break;
case RC_SAVE:
if (resultCode == RESULT_OK) {
Log.d(TAG, "Credential Save: OK");
showToast("Credential Save Success");
} else {
Log.e(TAG, "Credential Save: NOT OK");
showToast("Credential Save Failed");
}
mIsResolving = false;
break;
}
}</code></pre>
<p>The following will be Called when the save button is clicked. Reads the entries in the email and password
fields and attempts to save a new Credential to the Credentials API.</p>
<pre><code class="language-java"> private void saveCredentialClicked() {
String email = mEmailField.getText().toString();
String password = mPasswordField.getText().toString();
// Create a Credential with the user's email as the ID and storing the password. We
// could also add 'Name' and 'ProfilePictureURL' but that is outside the scope of this
// minimal sample.
Log.d(TAG, "Saving Credential:" + email + ":" + anonymizePassword(password));
final Credential credential = new Credential.Builder(email)
.setPassword(password)
.build();
showProgress();
// NOTE: this method unconditionally saves the Credential built, even if all the fields
// are blank or it is invalid in some other way. In a real application you should contact
// your app's back end and determine that the credential is valid before saving it to the
// Credentials backend.
showProgress();
mCredentialsClient.save(credential).addOnCompleteListener(
new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
showToast("Credential saved.");
hideProgress();
return;
}
Exception e = task.getException();
if (e instanceof ResolvableApiException) {
// The first time a credential is saved, the user is shown UI
// to confirm the action. This requires resolution.
ResolvableApiException rae = (ResolvableApiException) e;
resolveResult(rae, RC_SAVE);
} else {
// Save failure cannot be resolved.
Log.w(TAG, "Save failed.", e);
showToast("Credential Save Failed");
hideProgress();
}
}
});
}</code></pre>
<p>The following will be Called when the Load Credentials button is clicked. Attempts to read the user's saved Credentials from the Credentials API. This may show UX, such as a credential picker or an account picker.</p>
<blockquote>
<p><b>Note:</b> in a normal application loading credentials should happen without explicit user
action, this is only connected to a 'Load Credentials' button for easier demonstration
in this sample. Make sure not to load credentials automatically if the user has clicked
a "sign out" button in your application in order to avoid a sign-in loop. You can do this
with the function <code>Auth.CredentialsApi.disableAuthSignIn(...)</code>.</p>
</blockquote>
<pre><code class="language-java"> private void loadCredentialsClicked() {
requestCredentials();
}</code></pre>
<p>The following will be Called when the Load Hints button is clicked. Requests a Credential "hint" which will be the basic profile information and an ID token for an account on the device. This is useful to auto-fill sign-up forms with an email address, picture, and name or to do password-free authentication with a server by providing an ID Token.</p>
<pre><code class="language-java"> private void loadHintClicked() {
HintRequest hintRequest = new HintRequest.Builder()
.setHintPickerConfig(new CredentialPickerConfig.Builder()
.setShowCancelButton(true)
.build())
.setIdTokenRequested(shouldRequestIdToken())
.setEmailAddressIdentifierSupported(true)
.setAccountTypes(IdentityProviders.GOOGLE)
.build();
;
PendingIntent intent = mCredentialsClient.getHintPickerIntent(hintRequest);
try {
startIntentSenderForResult(intent.getIntentSender(), RC_HINT, null, 0, 0, 0);
mIsResolving = true;
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Could not start hint picker Intent", e);
mIsResolving = false;
}
}</code></pre>
<p>Here is how we request Credentials from the Credentials API.</p>
<pre><code class="language-java"> private void requestCredentials() {
// Request all of the user's saved username/password credentials. We are not using
// setAccountTypes so we will not load any credentials from other Identity Providers.
CredentialRequest request = new CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.setIdTokenRequested(shouldRequestIdToken())
.build();
showProgress();
mCredentialsClient.request(request).addOnCompleteListener(
new OnCompleteListener<CredentialRequestResponse>() {
@Override
public void onComplete(@NonNull Task<CredentialRequestResponse> task) {
hideProgress();
if (task.isSuccessful()) {
// Successfully read the credential without any user interaction, this
// means there was only a single credential and the user has auto
// sign-in enabled.
processRetrievedCredential(task.getResult().getCredential(), false);
return;
}
Exception e = task.getException();
if (e instanceof ResolvableApiException) {
// This is most likely the case where the user has multiple saved
// credentials and needs to pick one. This requires showing UI to
// resolve the read request.
ResolvableApiException rae = (ResolvableApiException) e;
resolveResult(rae, RC_READ);
return;
}
if (e instanceof ApiException) {
ApiException ae = (ApiException) e;
if (ae.getStatusCode() == CommonStatusCodes.SIGN_IN_REQUIRED) {
// This means only a hint is available, but we are handling that
// elsewhere so no need to act here.
} else {
Log.w(TAG, "Unexpected status code: " + ae.getStatusCode());
}
}
}
});
}</code></pre>
<p>The following wll be called when the delete credentials button is clicked. This deletes the last Credential that was loaded using the load button.</p>
<pre><code class="language-java"> private void deleteLoadedCredentialClicked() {
if (mCurrentCredential == null) {
showToast("Error: no credential to delete");
return;
}
showProgress();
mCredentialsClient.delete(mCurrentCredential).addOnCompleteListener(
new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
hideProgress();
if (task.isSuccessful()) {
// Credential delete succeeded, disable the delete button because we
// cannot delete the same credential twice. Clear text fields.
showToast("Credential Delete Success");
((EditText) findViewById(R.id.edit_text_email)).setText("");
((EditText) findViewById(R.id.edit_text_password)).setText("");
mCurrentCredential = null;
} else {
// Credential deletion either failed or was cancelled, this operation
// never gives a 'resolution' so we can display the failure message
// immediately.
Log.e(TAG, "Credential Delete: NOT OK", task.getException());
showToast("Credential Delete Failed");
}
}
});
}</code></pre>
<p>We attempt to resolve a non-successful result from an asynchronous request.</p>
<ul>
<li>@param rae the ResolvableApiException to resolve.</li>
<li>@param requestCode the request code to use when starting an Activity for result, this will be passed back to onActivityResult.</li>
</ul>
<pre><code class="language-java"> private void resolveResult(ResolvableApiException rae, 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: " + rae);
try {
rae.startResolutionForResult(MainActivity.this, requestCode);
mIsResolving = true;
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "STATUS: Failed to send resolution.", e);
hideProgress();
}
}
Here is how we Process a Credential object retrieved from a successful request.
* @param credential the Credential to process.
* @param isHint true if the Credential is hint-only, false otherwise.
```java
private void processRetrievedCredential(Credential credential, boolean isHint) {
Log.d(TAG, "Credential Retrieved: " + credential.getId() + ":" +
anonymizePassword(credential.getPassword()));
// If the Credential is not a hint, we should store it an enable the delete button.
// If it is a hint, skip this because a hint cannot be deleted.
if (!isHint) {
showToast("Credential Retrieved");
mCurrentCredential = credential;
findViewById(R.id.button_delete_loaded_credential).setEnabled(true);
} else {
showToast("Credential Hint Retrieved");
}
mEmailField.setText(credential.getId());
mPasswordField.setText(credential.getPassword());
if (!credential.getIdTokens().isEmpty()) {
IdToken idToken = credential.getIdTokens().get(0);
// For the purposes of this sample we are using a background service in place of a real
// web server. The client sends the Id Token to the server which uses the Google
// APIs Client Library for Java to verify the token and gain a signed assertion
// of the user's email address. This can be used to confirm the user's identity
// and sign the user in even without providing a password.
Intent intent = new Intent(this, MockServer.class)
.putExtra(MockServer.EXTRA_IDTOKEN, idToken.getIdToken());
startService(intent);
} else {
// This state is reached if non-Google accounts are added to Gmail:
// https://support.google.com/mail/answer/6078445
Log.d(TAG, "Credential does not contain ID Tokens.");
}
}
We Determine if we should request an ID token with Hints/Credentials. The default behavior is to not request an ID token (for speed purposes) but by setting this value to true an ID token will be returned with Hints/Credentials when possible.
private boolean shouldRequestIdToken() {
return ((CheckBox) findViewById(R.id.checkbox_request_idtoken)).isChecked();
}
We encode a password into asterisks of the right length, for logging:
private String anonymizePassword(String password) {
if (password == null) {
return "null";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < password.length(); i++) {
sb.append('*');
}
return sb.toString();
}
Display a short Toast message
private void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
Show progress spinner and disable buttons
private void showProgress() {
findViewById(R.id.progress_bar).setVisibility(View.VISIBLE);
// Disable all buttons while progress indicator shows.
setViewsEnabled(false, R.id.button_load_credentials, R.id.button_load_hint,
R.id.button_save_credential, R.id.button_delete_loaded_credential);
}
Hide progress spinner and enable buttons :
private void hideProgress() {
findViewById(R.id.progress_bar).setVisibility(View.INVISIBLE);
// Enable buttons once progress indicator is hidden.
setViewsEnabled(true, R.id.button_load_credentials, R.id.button_load_hint,
R.id.button_save_credential);
}
Enable or disable multiple views
private void setViewsEnabled(boolean enabled, int... ids) {
for (int id : ids) {
findViewById(id).setEnabled(enabled);
}
}
Handle clicks:
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_save_credential:
saveCredentialClicked();
break;
case R.id.button_load_credentials:
loadCredentialsClicked();
break;
case R.id.button_load_hint:
loadHintClicked();
break;
case R.id.button_delete_loaded_credential:
deleteLoadedCredentialClicked();
break;
}
}
}
Here is the full code:
MainActivity.java
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
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.CredentialRequestResponse;
import com.google.android.gms.auth.api.credentials.Credentials;
import com.google.android.gms.auth.api.credentials.CredentialsClient;
import com.google.android.gms.auth.api.credentials.CredentialsOptions;
import com.google.android.gms.auth.api.credentials.HintRequest;
import com.google.android.gms.auth.api.credentials.IdToken;
import com.google.android.gms.auth.api.credentials.IdentityProviders;
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.Task;
/**
* A minimal example of saving and loading username/password credentials from the Credentials API.
* @author samstern@google.com
*/
public class MainActivity extends AppCompatActivity implements
View.OnClickListener {
private static final String TAG = MainActivity.class.getSimpleName();
private static final String KEY_IS_RESOLVING = "is_resolving";
private static final int RC_SAVE = 1;
private static final int RC_HINT = 2;
private static final int RC_READ = 3;
private EditText mEmailField;
private EditText mPasswordField;
private CredentialsClient mCredentialsClient;
private Credential mCurrentCredential;
private boolean mIsResolving = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
// Fields
mEmailField = findViewById(R.id.edit_text_email);
mPasswordField = findViewById(R.id.edit_text_password);
// Buttons
findViewById(R.id.button_save_credential).setOnClickListener(this);
findViewById(R.id.button_load_credentials).setOnClickListener(this);
findViewById(R.id.button_load_hint).setOnClickListener(this);
findViewById(R.id.button_delete_loaded_credential).setOnClickListener(this);
// Instance state
if (savedInstanceState != null) {
mIsResolving = savedInstanceState.getBoolean(KEY_IS_RESOLVING);
}
// Instantiate client for interacting with the credentials API. For this demo
// application we forcibly enable the SmartLock save dialog, which is sometimes
// disabled when it would conflict with the Android autofill API.
CredentialsOptions options = new CredentialsOptions.Builder()
.forceEnableSaveDialog()
.build();
mCredentialsClient = Credentials.getClient(this, options);
}
@Override
public void onStart() {
super.onStart();
// Attempt auto-sign in.
if (!mIsResolving) {
requestCredentials();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_IS_RESOLVING, mIsResolving);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "onActivityResult:" + requestCode + ":" + resultCode + ":" + data);
hideProgress();
switch (requestCode) {
case RC_HINT:
// Drop into handling for RC_READ
case RC_READ:
if (resultCode == RESULT_OK) {
boolean isHint = (requestCode == RC_HINT);
Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
processRetrievedCredential(credential, isHint);
} else {
Log.e(TAG, "Credential Read: NOT OK");
showToast("Credential Read Failed");
}
mIsResolving = false;
break;
case RC_SAVE:
if (resultCode == RESULT_OK) {
Log.d(TAG, "Credential Save: OK");
showToast("Credential Save Success");
} else {
Log.e(TAG, "Credential Save: NOT OK");
showToast("Credential Save Failed");
}
mIsResolving = false;
break;
}
}
/**
* Called when the save button is clicked. Reads the entries in the email and password
* fields and attempts to save a new Credential to the Credentials API.
*/
private void saveCredentialClicked() {
String email = mEmailField.getText().toString();
String password = mPasswordField.getText().toString();
// Create a Credential with the user's email as the ID and storing the password. We
// could also add 'Name' and 'ProfilePictureURL' but that is outside the scope of this
// minimal sample.
Log.d(TAG, "Saving Credential:" + email + ":" + anonymizePassword(password));
final Credential credential = new Credential.Builder(email)
.setPassword(password)
.build();
showProgress();
// NOTE: this method unconditionally saves the Credential built, even if all the fields
// are blank or it is invalid in some other way. In a real application you should contact
// your app's back end and determine that the credential is valid before saving it to the
// Credentials backend.
showProgress();
mCredentialsClient.save(credential).addOnCompleteListener(
new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
showToast("Credential saved.");
hideProgress();
return;
}
Exception e = task.getException();
if (e instanceof ResolvableApiException) {
// The first time a credential is saved, the user is shown UI
// to confirm the action. This requires resolution.
ResolvableApiException rae = (ResolvableApiException) e;
resolveResult(rae, RC_SAVE);
} else {
// Save failure cannot be resolved.
Log.w(TAG, "Save failed.", e);
showToast("Credential Save Failed");
hideProgress();
}
}
});
}
/**
* Called when the Load Credentials button is clicked. Attempts to read the user's saved
* Credentials from the Credentials API. This may show UX, such as a credential picker
* or an account picker.
*
* <b>Note:</b> in a normal application loading credentials should happen without explicit user
* action, this is only connected to a 'Load Credentials' button for easier demonstration
* in this sample. Make sure not to load credentials automatically if the user has clicked
* a "sign out" button in your application in order to avoid a sign-in loop. You can do this
* with the function <code>Auth.CredentialsApi.disableAuthSignIn(...)</code>.
*/
private void loadCredentialsClicked() {
requestCredentials();
}
/**
* Called when the Load Hints button is clicked. Requests a Credential "hint" which will
* be the basic profile information and an ID token for an account on the device. This is useful
* to auto-fill sign-up forms with an email address, picture, and name or to do password-free
* authentication with a server by providing an ID Token.
*/
private void loadHintClicked() {
HintRequest hintRequest = new HintRequest.Builder()
.setHintPickerConfig(new CredentialPickerConfig.Builder()
.setShowCancelButton(true)
.build())
.setIdTokenRequested(shouldRequestIdToken())
.setEmailAddressIdentifierSupported(true)
.setAccountTypes(IdentityProviders.GOOGLE)
.build();
;
PendingIntent intent = mCredentialsClient.getHintPickerIntent(hintRequest);
try {
startIntentSenderForResult(intent.getIntentSender(), RC_HINT, null, 0, 0, 0);
mIsResolving = true;
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Could not start hint picker Intent", e);
mIsResolving = false;
}
}
/**
* Request Credentials from the Credentials API.
*/
private void requestCredentials() {
// Request all of the user's saved username/password credentials. We are not using
// setAccountTypes so we will not load any credentials from other Identity Providers.
CredentialRequest request = new CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.setIdTokenRequested(shouldRequestIdToken())
.build();
showProgress();
mCredentialsClient.request(request).addOnCompleteListener(
new OnCompleteListener<CredentialRequestResponse>() {
@Override
public void onComplete(@NonNull Task<CredentialRequestResponse> task) {
hideProgress();
if (task.isSuccessful()) {
// Successfully read the credential without any user interaction, this
// means there was only a single credential and the user has auto
// sign-in enabled.
processRetrievedCredential(task.getResult().getCredential(), false);
return;
}
Exception e = task.getException();
if (e instanceof ResolvableApiException) {
// This is most likely the case where the user has multiple saved
// credentials and needs to pick one. This requires showing UI to
// resolve the read request.
ResolvableApiException rae = (ResolvableApiException) e;
resolveResult(rae, RC_READ);
return;
}
if (e instanceof ApiException) {
ApiException ae = (ApiException) e;
if (ae.getStatusCode() == CommonStatusCodes.SIGN_IN_REQUIRED) {
// This means only a hint is available, but we are handling that
// elsewhere so no need to act here.
} else {
Log.w(TAG, "Unexpected status code: " + ae.getStatusCode());
}
}
}
});
}
/**
* Called when the delete credentials button is clicked. This deletes the last Credential
* that was loaded using the load button.
*/
private void deleteLoadedCredentialClicked() {
if (mCurrentCredential == null) {
showToast("Error: no credential to delete");
return;
}
showProgress();
mCredentialsClient.delete(mCurrentCredential).addOnCompleteListener(
new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
hideProgress();
if (task.isSuccessful()) {
// Credential delete succeeded, disable the delete button because we
// cannot delete the same credential twice. Clear text fields.
showToast("Credential Delete Success");
((EditText) findViewById(R.id.edit_text_email)).setText("");
((EditText) findViewById(R.id.edit_text_password)).setText("");
mCurrentCredential = null;
} else {
// Credential deletion either failed or was cancelled, this operation
// never gives a 'resolution' so we can display the failure message
// immediately.
Log.e(TAG, "Credential Delete: NOT OK", task.getException());
showToast("Credential Delete Failed");
}
}
});
}
/**
* Attempt to resolve a non-successful result from an asynchronous request.
* @param rae the ResolvableApiException to resolve.
* @param requestCode the request code to use when starting an Activity for result,
* this will be passed back to onActivityResult.
*/
private void resolveResult(ResolvableApiException rae, 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: " + rae);
try {
rae.startResolutionForResult(MainActivity.this, requestCode);
mIsResolving = true;
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "STATUS: Failed to send resolution.", e);
hideProgress();
}
}
/**
* Process a Credential object retrieved from a successful request.
* @param credential the Credential to process.
* @param isHint true if the Credential is hint-only, false otherwise.
*/
private void processRetrievedCredential(Credential credential, boolean isHint) {
Log.d(TAG, "Credential Retrieved: " + credential.getId() + ":" +
anonymizePassword(credential.getPassword()));
// If the Credential is not a hint, we should store it an enable the delete button.
// If it is a hint, skip this because a hint cannot be deleted.
if (!isHint) {
showToast("Credential Retrieved");
mCurrentCredential = credential;
findViewById(R.id.button_delete_loaded_credential).setEnabled(true);
} else {
showToast("Credential Hint Retrieved");
}
mEmailField.setText(credential.getId());
mPasswordField.setText(credential.getPassword());
if (!credential.getIdTokens().isEmpty()) {
IdToken idToken = credential.getIdTokens().get(0);
// For the purposes of this sample we are using a background service in place of a real
// web server. The client sends the Id Token to the server which uses the Google
// APIs Client Library for Java to verify the token and gain a signed assertion
// of the user's email address. This can be used to confirm the user's identity
// and sign the user in even without providing a password.
Intent intent = new Intent(this, MockServer.class)
.putExtra(MockServer.EXTRA_IDTOKEN, idToken.getIdToken());
startService(intent);
} else {
// This state is reached if non-Google accounts are added to Gmail:
// https://support.google.com/mail/answer/6078445
Log.d(TAG, "Credential does not contain ID Tokens.");
}
}
/**
* Determine if we should request an ID token with Hints/Credentials. The default behavior
* is to not request an ID token (for speed purposes) but by setting this value to true
* an ID token will be returned with Hints/Credentials when possible.
*/
private boolean shouldRequestIdToken() {
return ((CheckBox) findViewById(R.id.checkbox_request_idtoken)).isChecked();
}
/** Make a password into asterisks of the right length, for logging. **/
private String anonymizePassword(String password) {
if (password == null) {
return "null";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < password.length(); i++) {
sb.append('*');
}
return sb.toString();
}
/** Display a short Toast message **/
private void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
/** Show progress spinner and disable buttons **/
private void showProgress() {
findViewById(R.id.progress_bar).setVisibility(View.VISIBLE);
// Disable all buttons while progress indicator shows.
setViewsEnabled(false, R.id.button_load_credentials, R.id.button_load_hint,
R.id.button_save_credential, R.id.button_delete_loaded_credential);
}
/** Hide progress spinner and enable buttons **/
private void hideProgress() {
findViewById(R.id.progress_bar).setVisibility(View.INVISIBLE);
// Enable buttons once progress indicator is hidden.
setViewsEnabled(true, R.id.button_load_credentials, R.id.button_load_hint,
R.id.button_save_credential);
}
/** Enable or disable multiple views **/
private void setViewsEnabled(boolean enabled, int... ids) {
for (int id : ids) {
findViewById(id).setEnabled(enabled);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_save_credential:
saveCredentialClicked();
break;
case R.id.button_load_credentials:
loadCredentialsClicked();
break;
case R.id.button_load_hint:
loadHintClicked();
break;
case R.id.button_delete_loaded_credential:
deleteLoadedCredentialClicked();
break;
}
}
}
Run
Copy the code or download it in the link below, build and run.
Reference
Here are the reference links:
Number | Link |
---|---|
1. | Download Example |
2. | Follow code author |
3. | Code: Apache 2.0 License |