Skip to content

Commit

Permalink
[local-authentication] Use BiometricPrompt instead of FingerprintMana…
Browse files Browse the repository at this point in the history
…ger (expo#6846)
  • Loading branch information
LinusU authored and tsapeta committed Jan 28, 2020
1 parent 5f19297 commit 9b7cd4e
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 78 deletions.
1 change: 1 addition & 0 deletions packages/expo-local-authentication/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,6 @@ if (new File(rootProject.projectDir.parentFile, 'package.json').exists()) {
dependencies {
unimodule "unimodules-core"

api "androidx.biometric:biometric:1.0.1"
api "androidx.core:core:1.0.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,63 @@

package expo.modules.localauthentication;

import android.app.Activity;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.Bundle;
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
import androidx.core.os.CancellationSignal;

import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
import androidx.fragment.app.FragmentActivity;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import org.unimodules.core.ExportedModule;
import org.unimodules.core.ModuleRegistry;
import org.unimodules.core.Promise;
import org.unimodules.core.interfaces.ActivityProvider;
import org.unimodules.core.interfaces.ExpoMethod;
import org.unimodules.core.interfaces.services.UIManager;

public class LocalAuthenticationModule extends ExportedModule {
private final FingerprintManagerCompat mFingerprintManager;
private final BiometricManager mBiometricManager;
private CancellationSignal mCancellationSignal;
private Promise mPromise;
private boolean mIsAuthenticating = false;
private ModuleRegistry mModuleRegistry;
private UIManager mUIManager;

private static final int AUTHENTICATION_TYPE_FINGERPRINT = 1;

private final FingerprintManagerCompat.AuthenticationCallback mAuthenticationCallback =
new FingerprintManagerCompat.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
mIsAuthenticating = false;
Bundle successResult = new Bundle();
successResult.putBoolean("success", true);
safeResolve(successResult);
}

@Override
public void onAuthenticationFailed() {
mIsAuthenticating = false;
Bundle failResult = new Bundle();
failResult.putBoolean("success", false);
failResult.putString("error", "authentication_failed");
safeResolve(failResult);
// Failed authentication doesn't stop the authentication process, stop it anyway so it works
// with the promise API.
safeCancel();
}

@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
mIsAuthenticating = false;
Bundle errorResult = new Bundle();
errorResult.putBoolean("success", false);
errorResult.putString("error", convertErrorCode(errMsgId));
errorResult.putString("message", errString.toString());
safeResolve(errorResult);
}

@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
mIsAuthenticating = false;
Bundle helpResult = new Bundle();
helpResult.putBoolean("success", false);
helpResult.putString("error", convertHelpCode(helpMsgId));
helpResult.putString("message", helpString.toString());
safeResolve(helpResult);
// Help doesn't stop the authentication process, stop it anyway so it works with the
// promise API.
safeCancel();
}
};
private final BiometricPrompt.AuthenticationCallback mAuthenticationCallback =
new BiometricPrompt.AuthenticationCallback () {
@Override
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
mIsAuthenticating = false;
Bundle successResult = new Bundle();
successResult.putBoolean("success", true);
safeResolve(successResult);
}

@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
mIsAuthenticating = false;
Bundle errorResult = new Bundle();
errorResult.putBoolean("success", false);
errorResult.putString("error", convertErrorCode(errMsgId));
errorResult.putString("message", errString.toString());
safeResolve(errorResult);
}
};

public LocalAuthenticationModule(Context context) {
super(context);

mFingerprintManager = FingerprintManagerCompat.from(context);
mBiometricManager = BiometricManager.from(context);
}

@Override
Expand All @@ -85,34 +68,40 @@ public String getName() {

@Override
public void onCreate(ModuleRegistry moduleRegistry) {
mModuleRegistry = moduleRegistry;
mUIManager = moduleRegistry.getModule(UIManager.class);
}

@ExpoMethod
public void supportedAuthenticationTypesAsync(final Promise promise) {
boolean hasHardware = mFingerprintManager.isHardwareDetected();
int result = mBiometricManager.canAuthenticate();
List<Integer> results = new ArrayList<>();
if (hasHardware) {
if (result != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
results.add(AUTHENTICATION_TYPE_FINGERPRINT);
}
promise.resolve(results);
}

@ExpoMethod
public void hasHardwareAsync(final Promise promise) {
boolean hasHardware = mFingerprintManager.isHardwareDetected();
promise.resolve(hasHardware);
int result = mBiometricManager.canAuthenticate();
promise.resolve(result != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE);
}

@ExpoMethod
public void isEnrolledAsync(final Promise promise) {
boolean isEnrolled = mFingerprintManager.hasEnrolledFingerprints();
promise.resolve(isEnrolled);
int result = mBiometricManager.canAuthenticate();
promise.resolve(result == BiometricManager.BIOMETRIC_SUCCESS);
}

@ExpoMethod
public void authenticateAsync(final Promise promise) {
// FingerprintManager callbacks are invoked on the main thread so also run this there to avoid
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
promise.reject("E_NOT_SUPPORTED", "Cannot display biometric prompt on android versions below 6.0");
return;
}

// BiometricPrompt callbacks are invoked on the main thread so also run this there to avoid
// having to do locking.
mUIManager.runOnUiQueueThread(new Runnable() {
@Override
Expand All @@ -129,7 +118,16 @@ public void run() {
mIsAuthenticating = true;
mPromise = promise;
mCancellationSignal = new CancellationSignal();
mFingerprintManager.authenticate(null, 0, mCancellationSignal, mAuthenticationCallback, null);

FragmentActivity fragmentActivity = (FragmentActivity) getCurrentActivity();
Executor executor = Executors.newSingleThreadExecutor();
BiometricPrompt biometricPrompt = new BiometricPrompt(fragmentActivity, executor, mAuthenticationCallback);

BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setDeviceCredentialAllowed(true)
.setTitle("Authenticate")
.build();
biometricPrompt.authenticate(promptInfo);
}
});
}
Expand Down Expand Up @@ -160,37 +158,31 @@ private void safeResolve(Object result) {

private static String convertErrorCode(int code) {
switch (code) {
case FingerprintManager.FINGERPRINT_ERROR_CANCELED:
case BiometricPrompt.ERROR_CANCELED:
case BiometricPrompt.ERROR_NEGATIVE_BUTTON:
case BiometricPrompt.ERROR_USER_CANCELED:
return "user_cancel";
case FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE:
case BiometricPrompt.ERROR_HW_NOT_PRESENT:
case BiometricPrompt.ERROR_HW_UNAVAILABLE:
case BiometricPrompt.ERROR_NO_BIOMETRICS:
case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL:
return "not_available";
case FingerprintManager.FINGERPRINT_ERROR_LOCKOUT:
case BiometricPrompt.ERROR_LOCKOUT:
case BiometricPrompt.ERROR_LOCKOUT_PERMANENT:
return "lockout";
case FingerprintManager.FINGERPRINT_ERROR_NO_SPACE:
case BiometricPrompt.ERROR_NO_SPACE:
return "no_space";
case FingerprintManager.FINGERPRINT_ERROR_TIMEOUT:
case BiometricPrompt.ERROR_TIMEOUT:
return "timeout";
case FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS:
case BiometricPrompt.ERROR_UNABLE_TO_PROCESS:
return "unable_to_process";
default:
return "unknown";
}
}

private static String convertHelpCode(int code) {
switch (code) {
case FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY:
return "imager_dirty";
case FingerprintManager.FINGERPRINT_ACQUIRED_INSUFFICIENT:
return "insufficient";
case FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL:
return "partial";
case FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST:
return "too_fast";
case FingerprintManager.FINGERPRINT_ACQUIRED_TOO_SLOW:
return "too_slow";
default:
return "unknown";
}
private Activity getCurrentActivity() {
ActivityProvider activityProvider = mModuleRegistry.getModule(ActivityProvider.class);
return activityProvider != null ? activityProvider.getCurrentActivity() : null;
}
}

0 comments on commit 9b7cd4e

Please sign in to comment.