In this tutorial, we’ll be discussing the Android Fingerprint API and implement a Fingerprint Dialog in our android application.
Android Fingerprint Manager
Fingerprint Manager is the class used to access the Fingerprint hardware from the device (if it exists).
Google recommends authenticating fingerprint in applications by displaying a DialogFragment with a Fingerprint icon to the user.
In order to implement Fingerprint Authentication, you need to add the following permission in the AndroidManifest.xml
file:
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
Following are the steps to implement Fingerprint Authentication in your application:
- Check whether there is a secure lock on the lock screen
- Check whether the Fingerprint Hardware is available using the
FingerprintManager
class. - Check whether the user has enrolled at least one fingerprint.
- Get access to Android keystore to store a key used to initiate a Cipher.
- Start the Authentication Method and add the callback methods
The Android Keystore system lets you store cryptographic keys in a container to make it more difficult to extract from the device.
Project Structure
Code
The code for the activity_main.xml
layout is given below:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="START AUTHENTICATION"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
The code for the dialog_fingerprint.xml
is given below:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="24dp"
android:paddingTop="24dp"
android:paddingRight="24dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<TextView
android:id="@+id/titleTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Fingerprint Dialog"
android:textAppearance="?android:attr/textAppearanceLarge"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<TextView
android:id="@+id/subtitleTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Confirm fingerprint to continue."
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleTextView" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/subtitleTextView"
app:srcCompat="@drawable/ic_fingerprint_white_24dp" />
<TextView
android:id="@+id/errorTextView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:gravity="center_vertical"
android:text="Touch sensor"
app:layout_constraintBottom_toBottomOf="@id/fab"
app:layout_constraintLeft_toRightOf="@id/fab"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/fab" />
<LinearLayout
android:id="@+id/buttons"
style="?android:attr/buttonBarStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="end"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/fab">
<Button
android:id="@+id/btnCancel"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cancel" />
</LinearLayout>
</android.support.constraint.ConstraintLayout>
The FingerprintHelper.java class is where we define the methods for authentication and initialization of Fingerprint and related class objects:
package com.journaldev.androidfingerprintapi;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.CancellationSignal;
import android.support.v4.app.ActivityCompat;
public class FingerprintHelper extends FingerprintManager.AuthenticationCallback {
private Context mContext;
private FingerprintManager mFingerprintManager;
private CancellationSignal mCancellationSignal;
private Callback mCallback;
public FingerprintHelper(FingerprintManager fingerprintManager, Context context, Callback callback) {
mContext = context;
mFingerprintManager = fingerprintManager;
mCallback = callback;
}
public boolean isFingerprintAuthAvailable() {
return mFingerprintManager.isHardwareDetected()
&& mFingerprintManager.hasEnrolledFingerprints();
}
public void startAuthentication(FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject) {
if (!isFingerprintAuthAvailable())
return;
mCancellationSignal = new CancellationSignal();
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
return;
}
manager.authenticate(cryptoObject, mCancellationSignal, 0, this, null);
}
public void stopListening() {
if (mCancellationSignal != null) {
mCancellationSignal.cancel();
mCancellationSignal = null;
}
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
mCallback.onError(errString.toString());
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
mCallback.onHelp(helpString.toString());
}
@Override
public void onAuthenticationFailed() {
mCallback.onAuthenticated(false);
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
mCallback.onAuthenticated(true);
}
public interface Callback {
void onAuthenticated(boolean b);
void onError(String s);
void onHelp(String s);
}
}
manager.authenticate(cryptoObject, mCancellationSignal, 0, this, null);
starts the authentication.
The Callback interface is used to pass the information to the UI which eventually gets displayed to the user.
The code for the MainActivity.java class is given below:
package com.journaldev.androidfingerprintapi;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
Button button;
FingerprintManagerCompat managerCompat;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
button.setOnClickListener(this);
}
private void showFingerPrintDialog() {
FingerprintDialog fragment = new FingerprintDialog();
fragment.setContext(this);
fragment.show(getSupportFragmentManager(), "");
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
managerCompat = FingerprintManagerCompat.from(MainActivity.this);
if (managerCompat.isHardwareDetected() && managerCompat.hasEnrolledFingerprints()) {
showFingerPrintDialog();
} else {
Toast.makeText(getApplicationContext(), "Fingerprint not supported", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
managerCompat = FingerprintManagerCompat.from(MainActivity.this);
initialises the FingerprintCompat class object.
isHardwareDetected()
and hasEnrolledFingerprints()
are used to check whether the fingerprint authentication is possible before showing the Dialog.
The code for the FingerprintDialog.java is given below:
package com.journaldev.androidfingerprintapi;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.support.v4.app.DialogFragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
public class FingerprintDialog extends DialogFragment
implements FingerprintHelper.Callback {
Button mCancelButton;
public static final String DEFAULT_KEY_NAME = "default_key";
FingerprintManager mFingerprintManager;
private FingerprintManager.CryptoObject mCryptoObject;
private FingerprintHelper mFingerprintHelper;
KeyStore mKeyStore = null;
KeyGenerator mKeyGenerator = null;
KeyguardManager mKeyguardManager;
private Context mContext;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Material_Light_Dialog);
try {
mKeyStore = KeyStore.getInstance("AndroidKeyStore");
} catch (KeyStoreException e) {
e.printStackTrace();
}
try {
mKeyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
}
Cipher defaultCipher;
try {
defaultCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException("Failed to get an instance of Cipher", e);
}
mKeyguardManager = getContext().getSystemService(KeyguardManager.class);
mFingerprintManager = getContext().getSystemService(FingerprintManager.class);
mFingerprintHelper = new FingerprintHelper(mFingerprintManager, getContext(), this);
if (!mKeyguardManager.isKeyguardSecure()) {
Toast.makeText(getContext(),
"Lock screen not set up.n"
+ "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint",
Toast.LENGTH_LONG).show();
return;
}
createKey(DEFAULT_KEY_NAME);
if (initCipher(defaultCipher, DEFAULT_KEY_NAME)) {
mCryptoObject = new FingerprintManager.CryptoObject(defaultCipher);
}
}
private boolean initCipher(Cipher cipher, String keyName) {
try {
mKeyStore.load(null);
SecretKey key = (SecretKey) mKeyStore.getKey(keyName, null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (KeyPermanentlyInvalidatedException e) {
Toast.makeText(mContext, "Keys are invalidated after created. Retry the purchasen"
+ e.getMessage(),
Toast.LENGTH_LONG).show();
return false;
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
| NoSuchAlgorithmException | InvalidKeyException e) {
Toast.makeText(mContext, "Failed to init cipher", Toast.LENGTH_LONG).show();
return false;
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.dialog_fingerprint, container, false);
mCancelButton = v.findViewById(R.id.btnCancel);
mCancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dismiss();
}
});
return v;
}
@Override
public void onResume() {
super.onResume();
if (mCryptoObject != null) {
mFingerprintHelper.startAuthentication(mFingerprintManager, mCryptoObject);
}
}
@Override
public void onPause() {
super.onPause();
mFingerprintHelper.stopListening();
}
public void setContext(Context context) {
mContext = context;
}
public void createKey(String keyName) {
try {
mKeyStore.load(null);
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
mKeyGenerator.init(builder.build());
mKeyGenerator.generateKey();
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
| CertificateException | IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void onAuthenticated(boolean b) {
if (b) {
Toast.makeText(mContext.getApplicationContext(), "Auth success", Toast.LENGTH_LONG).show();
dismiss();
} else
Toast.makeText(mContext.getApplicationContext(), "Auth failed", Toast.LENGTH_LONG).show();
}
@Override
public void onError(String s) {
Toast.makeText(mContext.getApplicationContext(), s, Toast.LENGTH_LONG).show();
}
@Override
public void onHelp(String s) {
Toast.makeText(mContext.getApplicationContext(), "Auth help message:" + s, Toast.LENGTH_LONG).show();
}
}
setRetainInstance(true);
is used to prevent multiple instances of the DialogFragment from getting created when the config changes.
The output of the above application in action is given below:
So on my phone when I authenticated with the correct fingerprint, it showed the appropriate Toast message.
That brings an end to this tutorial. You can download the project from the link below:
Note: Fingerprint API is deprecated since API 28 and replaced by the BiometricPrompt. We’ll discuss that in another tutorial.