diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md
index 5178a9e3b22..edca3d412bd 100644
--- a/packages/local_auth/local_auth_android/CHANGELOG.md
+++ b/packages/local_auth/local_auth_android/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 2.0.0
+
+* **BREAKING CHANGES:**
+ * Switches to `LocalAuthException` for error reporting.
+ * Removes support for `useErrorDialogs`.
+ * Renames `biometricHint` to `signInHint` to reflect its usage.
+
## 1.0.53
* Removes obsolete code related to supporting SDK <24.
diff --git a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java
index a76b4c769c8..4da8d5f2c36 100644
--- a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java
+++ b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java
@@ -5,19 +5,10 @@
import android.annotation.SuppressLint;
import android.app.Activity;
-import android.app.AlertDialog;
import android.app.Application;
-import android.content.Context;
-import android.content.DialogInterface.OnClickListener;
-import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.provider.Settings;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
@@ -25,6 +16,8 @@
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
+import io.flutter.plugins.localauth.Messages.AuthResult;
+import io.flutter.plugins.localauth.Messages.AuthResultCode;
import java.util.concurrent.Executor;
/**
@@ -38,14 +31,12 @@ class AuthenticationHelper extends BiometricPrompt.AuthenticationCallback
/** The callback that handles the result of this authentication process. */
interface AuthCompletionHandler {
/** Called when authentication attempt is complete. */
- void complete(Messages.AuthResult authResult);
+ void complete(AuthResult authResult);
}
- // This is null when not using v2 embedding;
private final Lifecycle lifecycle;
private final FragmentActivity activity;
private final AuthCompletionHandler completionHandler;
- private final boolean useErrorDialogs;
private final Messages.AuthStrings strings;
private final BiometricPrompt.PromptInfo promptInfo;
private final boolean isAuthSticky;
@@ -65,14 +56,13 @@ interface AuthCompletionHandler {
this.completionHandler = completionHandler;
this.strings = strings;
this.isAuthSticky = options.getSticky();
- this.useErrorDialogs = options.getUseErrorDialgs();
this.uiThreadExecutor = new UiThreadExecutor();
BiometricPrompt.PromptInfo.Builder promptBuilder =
new BiometricPrompt.PromptInfo.Builder()
.setDescription(strings.getReason())
.setTitle(strings.getSignInTitle())
- .setSubtitle(strings.getBiometricHint())
+ .setSubtitle(strings.getSignInHint())
.setConfirmationRequired(options.getSensitiveTransaction());
int allowedAuthenticators =
@@ -120,58 +110,69 @@ private void stop() {
@SuppressLint("SwitchIntDef")
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
+ AuthResultCode code;
switch (errorCode) {
+ case BiometricPrompt.ERROR_USER_CANCELED:
+ code = AuthResultCode.USER_CANCELED;
+ break;
+ case BiometricPrompt.ERROR_NEGATIVE_BUTTON:
+ code = AuthResultCode.NEGATIVE_BUTTON;
+ break;
case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL:
- if (useErrorDialogs) {
- showGoToSettingsDialog(
- strings.getDeviceCredentialsRequiredTitle(),
- strings.getDeviceCredentialsSetupDescription());
- return;
- }
- completionHandler.complete(Messages.AuthResult.ERROR_NOT_AVAILABLE);
+ code = AuthResultCode.NO_CREDENTIALS;
break;
- case BiometricPrompt.ERROR_NO_SPACE:
case BiometricPrompt.ERROR_NO_BIOMETRICS:
- if (useErrorDialogs) {
- showGoToSettingsDialog(
- strings.getBiometricRequiredTitle(), strings.getGoToSettingsDescription());
- return;
- }
- completionHandler.complete(Messages.AuthResult.ERROR_NOT_ENROLLED);
+ code = AuthResultCode.NOT_ENROLLED;
break;
case BiometricPrompt.ERROR_HW_UNAVAILABLE:
+ code = AuthResultCode.HARDWARE_UNAVAILABLE;
+ break;
case BiometricPrompt.ERROR_HW_NOT_PRESENT:
- completionHandler.complete(Messages.AuthResult.ERROR_NOT_AVAILABLE);
+ code = AuthResultCode.NO_HARDWARE;
break;
case BiometricPrompt.ERROR_LOCKOUT:
- completionHandler.complete(Messages.AuthResult.ERROR_LOCKED_OUT_TEMPORARILY);
+ code = AuthResultCode.LOCKED_OUT_TEMPORARILY;
break;
case BiometricPrompt.ERROR_LOCKOUT_PERMANENT:
- completionHandler.complete(Messages.AuthResult.ERROR_LOCKED_OUT_PERMANENTLY);
+ code = AuthResultCode.LOCKED_OUT_PERMANENTLY;
break;
case BiometricPrompt.ERROR_CANCELED:
// If we are doing sticky auth and the activity has been paused,
// ignore this error. We will start listening again when resumed.
if (activityPaused && isAuthSticky) {
return;
- } else {
- completionHandler.complete(Messages.AuthResult.FAILURE);
}
+ code = AuthResultCode.SYSTEM_CANCELED;
+ break;
+ case BiometricPrompt.ERROR_TIMEOUT:
+ code = AuthResultCode.TIMEOUT;
+ break;
+ case BiometricPrompt.ERROR_NO_SPACE:
+ code = AuthResultCode.NO_SPACE;
+ break;
+ case BiometricPrompt.ERROR_SECURITY_UPDATE_REQUIRED:
+ code = AuthResultCode.SECURITY_UPDATE_REQUIRED;
break;
default:
- completionHandler.complete(Messages.AuthResult.FAILURE);
+ code = AuthResultCode.UNKNOWN_ERROR;
+ break;
}
+ completionHandler.complete(
+ new AuthResult.Builder().setCode(code).setErrorMessage(errString.toString()).build());
stop();
}
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
- completionHandler.complete(Messages.AuthResult.SUCCESS);
+ completionHandler.complete(new AuthResult.Builder().setCode(AuthResultCode.SUCCESS).build());
stop();
}
@Override
- public void onAuthenticationFailed() {}
+ public void onAuthenticationFailed() {
+ // No-op; this is called for incremental failures. Wait for a final
+ // resolution via the success or error callbacks.
+ }
/**
* If the activity is paused, we keep track because biometric dialog simply returns "User
@@ -205,34 +206,6 @@ public void onResume(@NonNull LifecycleOwner owner) {
onActivityResumed(null);
}
- // Suppress inflateParams lint because dialogs do not need to attach to a parent view.
- @SuppressLint("InflateParams")
- private void showGoToSettingsDialog(String title, String descriptionText) {
- View view = LayoutInflater.from(activity).inflate(R.layout.go_to_setting, null, false);
- TextView message = view.findViewById(R.id.fingerprint_required);
- TextView description = view.findViewById(R.id.go_to_setting_description);
- message.setText(title);
- description.setText(descriptionText);
- Context context = new ContextThemeWrapper(activity, R.style.AlertDialogCustom);
- OnClickListener goToSettingHandler =
- (dialog, which) -> {
- completionHandler.complete(Messages.AuthResult.FAILURE);
- stop();
- activity.startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS));
- };
- OnClickListener cancelHandler =
- (dialog, which) -> {
- completionHandler.complete(Messages.AuthResult.FAILURE);
- stop();
- };
- new AlertDialog.Builder(context)
- .setView(view)
- .setPositiveButton(strings.getGoToSettingsButton(), goToSettingHandler)
- .setNegativeButton(strings.getCancelButton(), cancelHandler)
- .setCancelable(false)
- .show();
- }
-
// Unused methods for activity lifecycle.
@Override
diff --git a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java
index 10b87611343..7b02af92382 100644
--- a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java
+++ b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java
@@ -4,13 +4,11 @@
package io.flutter.plugins.localauth;
-import static android.app.Activity.RESULT_OK;
import static android.content.Context.KEYGUARD_SERVICE;
import android.app.Activity;
import android.app.KeyguardManager;
import android.content.Context;
-import android.content.Intent;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -21,11 +19,11 @@
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter;
-import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.localauth.AuthenticationHelper.AuthCompletionHandler;
import io.flutter.plugins.localauth.Messages.AuthClassification;
import io.flutter.plugins.localauth.Messages.AuthOptions;
import io.flutter.plugins.localauth.Messages.AuthResult;
+import io.flutter.plugins.localauth.Messages.AuthResultCode;
import io.flutter.plugins.localauth.Messages.AuthStrings;
import io.flutter.plugins.localauth.Messages.LocalAuthApi;
import io.flutter.plugins.localauth.Messages.Result;
@@ -39,32 +37,14 @@
*
Instantiate this in an add to app scenario to gracefully handle activity and context changes.
*/
public class LocalAuthPlugin implements FlutterPlugin, ActivityAware, LocalAuthApi {
- private static final int LOCK_REQUEST_CODE = 221;
private Activity activity;
private AuthenticationHelper authHelper;
@VisibleForTesting final AtomicBoolean authInProgress = new AtomicBoolean(false);
- // These are null when not using v2 embedding.
private Lifecycle lifecycle;
private BiometricManager biometricManager;
private KeyguardManager keyguardManager;
- Result lockRequestResult;
- private final PluginRegistry.ActivityResultListener resultListener =
- new PluginRegistry.ActivityResultListener() {
- @Override
- public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == LOCK_REQUEST_CODE) {
- if (resultCode == RESULT_OK && lockRequestResult != null) {
- onAuthenticationCompleted(lockRequestResult, AuthResult.SUCCESS);
- } else {
- onAuthenticationCompleted(lockRequestResult, AuthResult.FAILURE);
- }
- lockRequestResult = null;
- }
- return false;
- }
- };
/**
* Default constructor for LocalAuthPlugin.
@@ -73,15 +53,21 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
*/
public LocalAuthPlugin() {}
+ @Override
public @NonNull Boolean isDeviceSupported() {
return isDeviceSecure() || canAuthenticateWithBiometrics();
}
+ @Override
public @NonNull Boolean deviceCanSupportBiometrics() {
return hasBiometricHardware();
}
+ @Override
public @NonNull List getEnrolledBiometrics() {
+ if (biometricManager == null) {
+ return null;
+ }
ArrayList biometrics = new ArrayList<>();
if (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
== BiometricManager.BIOMETRIC_SUCCESS) {
@@ -94,6 +80,7 @@ public LocalAuthPlugin() {}
return biometrics;
}
+ @Override
public @NonNull Boolean stopAuthentication() {
try {
if (authHelper != null && authInProgress.get()) {
@@ -107,27 +94,29 @@ public LocalAuthPlugin() {}
}
}
+ @Override
public void authenticate(
@NonNull AuthOptions options,
@NonNull AuthStrings strings,
@NonNull Result result) {
if (authInProgress.get()) {
- result.success(AuthResult.ERROR_ALREADY_IN_PROGRESS);
+ result.success(new AuthResult.Builder().setCode(AuthResultCode.ALREADY_IN_PROGRESS).build());
return;
}
if (activity == null || activity.isFinishing()) {
- result.success(AuthResult.ERROR_NO_ACTIVITY);
+ result.success(new AuthResult.Builder().setCode(AuthResultCode.NO_ACTIVITY).build());
return;
}
if (!(activity instanceof FragmentActivity)) {
- result.success(AuthResult.ERROR_NOT_FRAGMENT_ACTIVITY);
+ result.success(
+ new AuthResult.Builder().setCode(AuthResultCode.NOT_FRAGMENT_ACTIVITY).build());
return;
}
if (!isDeviceSupported()) {
- result.success(AuthResult.ERROR_NOT_AVAILABLE);
+ result.success(new AuthResult.Builder().setCode(AuthResultCode.NO_CREDENTIALS).build());
return;
}
@@ -221,7 +210,6 @@ private void setServicesFromActivity(Activity activity) {
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
- binding.addActivityResultListener(resultListener);
setServicesFromActivity(binding.getActivity());
lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding);
}
@@ -234,7 +222,6 @@ public void onDetachedFromActivityForConfigChanges() {
@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
- binding.addActivityResultListener(resultListener);
setServicesFromActivity(binding.getActivity());
lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding);
}
diff --git a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/Messages.java b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/Messages.java
index 5ed7cf45567..667d6656992 100644
--- a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/Messages.java
+++ b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/Messages.java
@@ -1,7 +1,7 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Autogenerated from Pigeon (v22.4.0), do not edit directly.
+// Autogenerated from Pigeon (v22.7.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
package io.flutter.plugins.localauth;
@@ -66,29 +66,49 @@ protected static ArrayList