[
IOSAuthMessages(),
AndroidAuthMessages(),
- WindowsAuthMessages()
+ WindowsAuthMessages(),
+ OhosAuthMessages()
],
AuthenticationOptions options = const AuthenticationOptions()}) {
return LocalAuthPlatform.instance.authenticate(
diff --git a/packages/local_auth/local_auth/pubspec.yaml b/packages/local_auth/local_auth/pubspec.yaml
index a367c96ca56d0bf7234f3d71d8d245c167321ea5..0a393d6211e38bea4b3f75a908034d3def6d24c8 100644
--- a/packages/local_auth/local_auth/pubspec.yaml
+++ b/packages/local_auth/local_auth/pubspec.yaml
@@ -3,11 +3,11 @@ description: Flutter plugin for Android and iOS devices to allow local
authentication via fingerprint, touch ID, face ID, passcode, pin, or pattern.
repository: https://github.com/flutter/packages/tree/main/packages/local_auth/local_auth
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22
-version: 2.1.7
+version: 2.1.6
environment:
- sdk: ">=3.0.0 <4.0.0"
- flutter: ">=3.10.0"
+ sdk: ">=2.14.0 <3.0.0"
+ flutter: ">=3.0.0"
flutter:
plugin:
@@ -18,6 +18,8 @@ flutter:
default_package: local_auth_ios
windows:
default_package: local_auth_windows
+ ohos:
+ default_package: local_auth_ohos
dependencies:
flutter:
@@ -26,16 +28,15 @@ dependencies:
local_auth_ios: ^1.0.1
local_auth_platform_interface: ^1.0.1
local_auth_windows: ^1.0.0
+ local_auth_ohos:
+ path: ../local_auth_ohos
dev_dependencies:
+ flutter_driver:
+ sdk: flutter
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
- mockito: 5.4.1
+ mockito: ^5.1.0
plugin_platform_interface: ^2.1.2
-
-topics:
- - authentication
- - biometrics
- - local-auth
diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md
index 9c62c872ca8a8507264284f6db87fc2c12dc8283..b74748c3f2e992990311f9c9577cb48b1e7135a2 100644
--- a/packages/local_auth/local_auth_android/CHANGELOG.md
+++ b/packages/local_auth/local_auth_android/CHANGELOG.md
@@ -1,68 +1,5 @@
-## 1.0.34
+## NEXT
-* Updates pigeon to 11.0.0 and removes enum wrappers.
-
-## 1.0.33
-
-* Adds pub topics to package metadata.
-* Updates minimum supported SDK version to Flutter 3.7/Dart 2.19.
-
-## 1.0.32
-
-* Fixes stale ignore: prefer_const_constructors.
-* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
-* Updates androidx.fragment version to 1.6.0.
-
-## 1.0.31
-
-* Updates androidx.fragment version to 1.5.7.
-* Updates androidx.core version to 1.10.1.
-
-## 1.0.30
-
-* Updates androidx.fragment version to 1.5.6
-
-## 1.0.29
-
-* Fixes a regression in 1.0.23 that caused canceled auths to return success.
-* Updates minimum supported SDK version to Flutter 3.3/Dart 2.18.
-
-## 1.0.28
-
-* Removes unused resources as indicated by Android lint warnings.
-
-## 1.0.27
-
-* Fixes compatibility with AGP versions older than 4.2.
-
-## 1.0.26
-
-* Adds `targetCompatibilty` matching `sourceCompatibility` for older toolchains.
-
-## 1.0.25
-
-* Adds a namespace for compatibility with AGP 8.0.
-
-## 1.0.24
-
-* Fixes `getEnrolledBiometrics` return value handling.
-
-## 1.0.23
-
-* Switches internals to Pigeon and fixes Java warnings.
-
-## 1.0.22
-
-* Sets an explicit Java compatibility version.
-
-## 1.0.21
-
-* Clarifies explanation of endorsement in README.
-* Aligns Dart and Flutter SDK constraints.
-
-## 1.0.20
-
-* Fixes compilation warnings.
* Updates compileSdkVersion to 33.
## 1.0.19
diff --git a/packages/local_auth/local_auth_android/README.md b/packages/local_auth/local_auth_android/README.md
index 047e82ef64b376c880adce9134baffe3b467a9bc..07244912f23100f3bcecc6c38ccfa4a7a1e86237 100644
--- a/packages/local_auth/local_auth_android/README.md
+++ b/packages/local_auth/local_auth_android/README.md
@@ -5,11 +5,7 @@ The Android implementation of [`local_auth`][1].
## Usage
This package is [endorsed][2], which means you can simply use `local_auth`
-normally. This package will be automatically included in your app when you do,
-so you do not need to add it to your `pubspec.yaml`.
-
-However, if you `import` this package to use any of its APIs directly, you
-should add it to your `pubspec.yaml` as usual.
+normally. This package will be automatically included in your app when you do.
[1]: https://pub.dev/packages/local_auth
-[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin
+[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin
\ No newline at end of file
diff --git a/packages/local_auth/local_auth_android/android/build.gradle b/packages/local_auth/local_auth_android/android/build.gradle
index b55df15f3c9bdd598a195793d2aafa60c3bfa2bd..8e116709d6cc19e4699ec93f9f4e2cee2b7fc3ec 100644
--- a/packages/local_auth/local_auth_android/android/build.gradle
+++ b/packages/local_auth/local_auth_android/android/build.gradle
@@ -22,26 +22,15 @@ rootProject.allprojects {
apply plugin: 'com.android.library'
android {
- // Conditional for compatibility with AGP <4.2.
- if (project.android.hasProperty("namespace")) {
- namespace 'io.flutter.plugins.localauth'
- }
compileSdkVersion 33
defaultConfig {
minSdkVersion 16
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-
lintOptions {
- checkAllWarnings true
- warningsAsErrors true
disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency'
+ baseline file("lint-baseline.xml")
}
@@ -59,16 +48,13 @@ android {
}
dependencies {
- api "androidx.core:core:1.10.1"
+ api "androidx.core:core:1.9.0"
api "androidx.biometric:biometric:1.1.0"
- api "androidx.fragment:fragment:1.6.0"
+ api "androidx.fragment:fragment:1.5.5"
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-inline:5.0.0'
- testImplementation 'org.robolectric:robolectric:4.10.3'
+ testImplementation 'org.robolectric:robolectric:4.5'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
- // TODO(camsim99): org.jetbrains.kotlin:kotlin-bom artifact purpose is to align kotlin stdlib and related code versions.
- // This should be removed when https://github.com/flutter/flutter/issues/125062 is fixed.
- implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.10"))
}
diff --git a/packages/local_auth/local_auth_android/android/src/main/AndroidManifest.xml b/packages/local_auth/local_auth_android/android/src/main/AndroidManifest.xml
index ef323b4281c098896f18756d2a615f1ddcb0f0b2..63f75079e00da9da09a97fcdf9269b6ecd06a9a1 100644
--- a/packages/local_auth/local_auth_android/android/src/main/AndroidManifest.xml
+++ b/packages/local_auth/local_auth_android/android/src/main/AndroidManifest.xml
@@ -1,4 +1,5 @@
+
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 fcb32c546b19861f60940b798fbc2148547e18fd..c30f879d2c7f990c08128da0136d12ed99c7d738 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
@@ -8,6 +8,7 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.app.Application;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.os.Bundle;
@@ -25,6 +26,7 @@ import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
+import io.flutter.plugin.common.MethodCall;
import java.util.concurrent.Executor;
/**
@@ -33,20 +35,35 @@ import java.util.concurrent.Executor;
* One instance per call is generated to ensure readable separation of executable paths across
* method calls.
*/
+@SuppressWarnings("deprecation")
class AuthenticationHelper extends BiometricPrompt.AuthenticationCallback
implements Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver {
/** The callback that handles the result of this authentication process. */
interface AuthCompletionHandler {
- /** Called when authentication attempt is complete. */
- void complete(Messages.AuthResult authResult);
+ /** Called when authentication was successful. */
+ void onSuccess();
+
+ /**
+ * Called when authentication failed due to user. For instance, when user cancels the auth or
+ * quits the app.
+ */
+ void onFailure();
+
+ /**
+ * Called when authentication fails due to non-user related problems such as system errors,
+ * phone not having a FP reader etc.
+ *
+ * @param code The error code to be returned to Flutter app.
+ * @param error The description of the error.
+ */
+ void onError(String code, String error);
}
// 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 MethodCall call;
private final BiometricPrompt.PromptInfo promptInfo;
private final boolean isAuthSticky;
private final UiThreadExecutor uiThreadExecutor;
@@ -56,24 +73,23 @@ class AuthenticationHelper extends BiometricPrompt.AuthenticationCallback
AuthenticationHelper(
Lifecycle lifecycle,
FragmentActivity activity,
- @NonNull Messages.AuthOptions options,
- @NonNull Messages.AuthStrings strings,
- @NonNull AuthCompletionHandler completionHandler,
+ MethodCall call,
+ AuthCompletionHandler completionHandler,
boolean allowCredentials) {
this.lifecycle = lifecycle;
this.activity = activity;
this.completionHandler = completionHandler;
- this.strings = strings;
- this.isAuthSticky = options.getSticky();
- this.useErrorDialogs = options.getUseErrorDialgs();
+ this.call = call;
+ this.isAuthSticky = call.argument("stickyAuth");
this.uiThreadExecutor = new UiThreadExecutor();
BiometricPrompt.PromptInfo.Builder promptBuilder =
new BiometricPrompt.PromptInfo.Builder()
- .setDescription(strings.getReason())
- .setTitle(strings.getSignInTitle())
- .setSubtitle(strings.getBiometricHint())
- .setConfirmationRequired(options.getSensitiveTransaction());
+ .setDescription((String) call.argument("localizedReason"))
+ .setTitle((String) call.argument("signInTitle"))
+ .setSubtitle((String) call.argument("biometricHint"))
+ .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction"))
+ .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction"));
int allowedAuthenticators =
BiometricManager.Authenticators.BIOMETRIC_WEAK
@@ -82,7 +98,7 @@ class AuthenticationHelper extends BiometricPrompt.AuthenticationCallback
if (allowCredentials) {
allowedAuthenticators |= BiometricManager.Authenticators.DEVICE_CREDENTIAL;
} else {
- promptBuilder.setNegativeButtonText(strings.getCancelButton());
+ promptBuilder.setNegativeButtonText((String) call.argument("cancelButton"));
}
promptBuilder.setAllowedAuthenticators(allowedAuthenticators);
@@ -119,35 +135,40 @@ class AuthenticationHelper extends BiometricPrompt.AuthenticationCallback
@SuppressLint("SwitchIntDef")
@Override
- public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
switch (errorCode) {
case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL:
- if (useErrorDialogs) {
+ if (call.argument("useErrorDialogs")) {
showGoToSettingsDialog(
- strings.getDeviceCredentialsRequiredTitle(),
- strings.getDeviceCredentialsSetupDescription());
+ (String) call.argument("deviceCredentialsRequired"),
+ (String) call.argument("deviceCredentialsSetupDescription"));
return;
}
- completionHandler.complete(Messages.AuthResult.ERROR_NOT_AVAILABLE);
+ completionHandler.onError("NotAvailable", "Security credentials not available.");
break;
case BiometricPrompt.ERROR_NO_SPACE:
case BiometricPrompt.ERROR_NO_BIOMETRICS:
- if (useErrorDialogs) {
+ if (call.argument("useErrorDialogs")) {
showGoToSettingsDialog(
- strings.getBiometricRequiredTitle(), strings.getGoToSettingsDescription());
+ (String) call.argument("biometricRequired"),
+ (String) call.argument("goToSettingDescription"));
return;
}
- completionHandler.complete(Messages.AuthResult.ERROR_NOT_ENROLLED);
+ completionHandler.onError("NotEnrolled", "No Biometrics enrolled on this device.");
break;
case BiometricPrompt.ERROR_HW_UNAVAILABLE:
case BiometricPrompt.ERROR_HW_NOT_PRESENT:
- completionHandler.complete(Messages.AuthResult.ERROR_NOT_AVAILABLE);
+ completionHandler.onError("NotAvailable", "Security credentials not available.");
break;
case BiometricPrompt.ERROR_LOCKOUT:
- completionHandler.complete(Messages.AuthResult.ERROR_LOCKED_OUT_TEMPORARILY);
+ completionHandler.onError(
+ "LockedOut",
+ "The operation was canceled because the API is locked out due to too many attempts. This occurs after 5 failed attempts, and lasts for 30 seconds.");
break;
case BiometricPrompt.ERROR_LOCKOUT_PERMANENT:
- completionHandler.complete(Messages.AuthResult.ERROR_LOCKED_OUT_PERMANENTLY);
+ completionHandler.onError(
+ "PermanentlyLockedOut",
+ "The operation was canceled because ERROR_LOCKOUT occurred too many times. Biometric authentication is disabled until the user unlocks with strong authentication (PIN/Pattern/Password)");
break;
case BiometricPrompt.ERROR_CANCELED:
// If we are doing sticky auth and the activity has been paused,
@@ -155,18 +176,18 @@ class AuthenticationHelper extends BiometricPrompt.AuthenticationCallback
if (activityPaused && isAuthSticky) {
return;
} else {
- completionHandler.complete(Messages.AuthResult.FAILURE);
+ completionHandler.onFailure();
}
break;
default:
- completionHandler.complete(Messages.AuthResult.FAILURE);
+ completionHandler.onFailure();
}
stop();
}
@Override
- public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
- completionHandler.complete(Messages.AuthResult.SUCCESS);
+ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
+ completionHandler.onSuccess();
stop();
}
@@ -191,7 +212,13 @@ class AuthenticationHelper extends BiometricPrompt.AuthenticationCallback
final BiometricPrompt prompt = new BiometricPrompt(activity, uiThreadExecutor, this);
// When activity is resuming, we cannot show the prompt right away. We need to post it to the
// UI queue.
- uiThreadExecutor.handler.post(() -> prompt.authenticate(promptInfo));
+ uiThreadExecutor.handler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ prompt.authenticate(promptInfo);
+ }
+ });
}
}
@@ -209,26 +236,32 @@ class AuthenticationHelper extends BiometricPrompt.AuthenticationCallback
@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);
+ TextView message = (TextView) view.findViewById(R.id.fingerprint_required);
+ TextView description = (TextView) 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));
+ new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ completionHandler.onFailure();
+ stop();
+ activity.startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS));
+ }
};
OnClickListener cancelHandler =
- (dialog, which) -> {
- completionHandler.complete(Messages.AuthResult.FAILURE);
- stop();
+ new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ completionHandler.onFailure();
+ stop();
+ }
};
new AlertDialog.Builder(context)
.setView(view)
- .setPositiveButton(strings.getGoToSettingsButton(), goToSettingHandler)
- .setNegativeButton(strings.getCancelButton(), cancelHandler)
+ .setPositiveButton((String) call.argument("goToSetting"), goToSettingHandler)
+ .setNegativeButton((String) call.argument("cancelButton"), cancelHandler)
.setCancelable(false)
.show();
}
@@ -262,7 +295,7 @@ class AuthenticationHelper extends BiometricPrompt.AuthenticationCallback
@Override
public void onCreate(@NonNull LifecycleOwner owner) {}
- static class UiThreadExecutor implements Executor {
+ private static class UiThreadExecutor implements Executor {
final Handler handler = new Handler(Looper.getMainLooper());
@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 7ed2c04a8f6ab8c4d3cc430773794c7f7843dae4..e545df01e7c0948feda76307f2e554add0633979 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
@@ -21,17 +21,14 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin;
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.MethodCall;
+import io.flutter.plugin.common.MethodChannel;
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
+import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry;
+import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.plugins.localauth.AuthenticationHelper.AuthCompletionHandler;
-import io.flutter.plugins.localauth.Messages.AuthClassification;
-import io.flutter.plugins.localauth.Messages.AuthClassificationWrapper;
-import io.flutter.plugins.localauth.Messages.AuthOptions;
-import io.flutter.plugins.localauth.Messages.AuthResult;
-import io.flutter.plugins.localauth.Messages.AuthStrings;
-import io.flutter.plugins.localauth.Messages.LocalAuthApi;
-import io.flutter.plugins.localauth.Messages.Result;
import java.util.ArrayList;
-import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -39,7 +36,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
*
*
Instantiate this in an add to app scenario to gracefully handle activity and context changes.
*/
-public class LocalAuthPlugin implements FlutterPlugin, ActivityAware, LocalAuthApi {
+@SuppressWarnings("deprecation")
+public class LocalAuthPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware {
+ private static final String CHANNEL_NAME = "plugins.flutter.io/local_auth_android";
private static final int LOCK_REQUEST_CODE = 221;
private Activity activity;
private AuthenticationHelper authHelper;
@@ -47,19 +46,20 @@ public class LocalAuthPlugin implements FlutterPlugin, ActivityAware, LocalAuthA
@VisibleForTesting final AtomicBoolean authInProgress = new AtomicBoolean(false);
// These are null when not using v2 embedding.
+ private MethodChannel channel;
private Lifecycle lifecycle;
private BiometricManager biometricManager;
private KeyguardManager keyguardManager;
- Result lockRequestResult;
+ private 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);
+ authenticateSuccess(lockRequestResult);
} else {
- onAuthenticationCompleted(lockRequestResult, AuthResult.FAILURE);
+ authenticateFail(lockRequestResult);
}
lockRequestResult = null;
}
@@ -73,13 +73,16 @@ public class LocalAuthPlugin implements FlutterPlugin, ActivityAware, LocalAuthA
* Calling this will register the plugin with the passed registrar. However, plugins
* initialized this way won't react to changes in activity or context.
*
- * @param registrar provides access to necessary plugin context.
+ * @param registrar attaches this plugin's {@link
+ * io.flutter.plugin.common.MethodChannel.MethodCallHandler} to the registrar's {@link
+ * io.flutter.plugin.common.BinaryMessenger}.
*/
@SuppressWarnings("deprecation")
- public static void registerWith(@NonNull PluginRegistry.Registrar registrar) {
+ public static void registerWith(Registrar registrar) {
+ final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME);
final LocalAuthPlugin plugin = new LocalAuthPlugin();
plugin.activity = registrar.activity();
- LocalAuthApi.setup(registrar.messenger(), plugin);
+ channel.setMethodCallHandler(plugin);
registrar.addActivityResultListener(plugin.resultListener);
}
@@ -90,112 +93,176 @@ public class LocalAuthPlugin implements FlutterPlugin, ActivityAware, LocalAuthA
*/
public LocalAuthPlugin() {}
- public @NonNull Boolean isDeviceSupported() {
- return isDeviceSecure() || canAuthenticateWithBiometrics();
- }
-
- public @NonNull Boolean deviceCanSupportBiometrics() {
- return hasBiometricHardware();
- }
-
- public @NonNull List getEnrolledBiometrics() {
- ArrayList biometrics = new ArrayList<>();
- if (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
- == BiometricManager.BIOMETRIC_SUCCESS) {
- biometrics.add(wrappedBiometric(AuthClassification.WEAK));
- }
- if (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
- == BiometricManager.BIOMETRIC_SUCCESS) {
- biometrics.add(wrappedBiometric(AuthClassification.STRONG));
- }
- return biometrics;
- }
-
- private @NonNull AuthClassificationWrapper wrappedBiometric(AuthClassification value) {
- return new AuthClassificationWrapper.Builder().setValue(value).build();
- }
-
- public @NonNull Boolean stopAuthentication() {
- try {
- if (authHelper != null && authInProgress.get()) {
- authHelper.stopAuthentication();
- authHelper = null;
- }
- authInProgress.set(false);
- return true;
- } catch (Exception e) {
- return false;
+ @Override
+ public void onMethodCall(MethodCall call, @NonNull final Result result) {
+ switch (call.method) {
+ case "authenticate":
+ authenticate(call, result);
+ break;
+ case "getEnrolledBiometrics":
+ getEnrolledBiometrics(result);
+ break;
+ case "isDeviceSupported":
+ isDeviceSupported(result);
+ break;
+ case "stopAuthentication":
+ stopAuthentication(result);
+ break;
+ case "deviceSupportsBiometrics":
+ deviceSupportsBiometrics(result);
+ break;
+ default:
+ result.notImplemented();
+ break;
}
}
- public void authenticate(
- @NonNull AuthOptions options,
- @NonNull AuthStrings strings,
- @NonNull Result result) {
+ /*
+ * Starts authentication process
+ */
+ private void authenticate(MethodCall call, final Result result) {
if (authInProgress.get()) {
- result.success(AuthResult.ERROR_ALREADY_IN_PROGRESS);
+ result.error("auth_in_progress", "Authentication in progress", null);
return;
}
if (activity == null || activity.isFinishing()) {
- result.success(AuthResult.ERROR_NO_ACTIVITY);
+ result.error("no_activity", "local_auth plugin requires a foreground activity", null);
return;
}
if (!(activity instanceof FragmentActivity)) {
- result.success(AuthResult.ERROR_NOT_FRAGMENT_ACTIVITY);
+ result.error(
+ "no_fragment_activity",
+ "local_auth plugin requires activity to be a FragmentActivity.",
+ null);
return;
}
if (!isDeviceSupported()) {
- result.success(AuthResult.ERROR_NOT_AVAILABLE);
+ authInProgress.set(false);
+ result.error("NotAvailable", "Required security features not enabled", null);
return;
}
authInProgress.set(true);
AuthCompletionHandler completionHandler = createAuthCompletionHandler(result);
- boolean allowCredentials = !options.getBiometricOnly() && canAuthenticateWithDeviceCredential();
+ boolean isBiometricOnly = call.argument("biometricOnly");
+ boolean allowCredentials = !isBiometricOnly && canAuthenticateWithDeviceCredential();
- sendAuthenticationRequest(options, strings, allowCredentials, completionHandler);
+ sendAuthenticationRequest(call, completionHandler, allowCredentials);
+ return;
}
@VisibleForTesting
- public @NonNull AuthCompletionHandler createAuthCompletionHandler(
- @NonNull final Result result) {
- return authResult -> onAuthenticationCompleted(result, authResult);
+ public AuthCompletionHandler createAuthCompletionHandler(final Result result) {
+ return new AuthCompletionHandler() {
+ @Override
+ public void onSuccess() {
+ authenticateSuccess(result);
+ }
+
+ @Override
+ public void onFailure() {
+ authenticateFail(result);
+ }
+
+ @Override
+ public void onError(String code, String error) {
+ if (authInProgress.compareAndSet(true, false)) {
+ result.error(code, error, null);
+ }
+ }
+ };
}
@VisibleForTesting
public void sendAuthenticationRequest(
- @NonNull AuthOptions options,
- @NonNull AuthStrings strings,
- boolean allowCredentials,
- @NonNull AuthCompletionHandler completionHandler) {
+ MethodCall call, AuthCompletionHandler completionHandler, boolean allowCredentials) {
authHelper =
new AuthenticationHelper(
- lifecycle,
- (FragmentActivity) activity,
- options,
- strings,
- completionHandler,
- allowCredentials);
+ lifecycle, (FragmentActivity) activity, call, completionHandler, allowCredentials);
authHelper.authenticate();
}
- void onAuthenticationCompleted(Result result, AuthResult value) {
+ private void authenticateSuccess(Result result) {
if (authInProgress.compareAndSet(true, false)) {
- result.success(value);
+ result.success(true);
+ }
+ }
+
+ private void authenticateFail(Result result) {
+ if (authInProgress.compareAndSet(true, false)) {
+ result.success(false);
+ }
+ }
+
+ /*
+ * Stops the authentication if in progress.
+ */
+ private void stopAuthentication(Result result) {
+ try {
+ if (authHelper != null && authInProgress.get()) {
+ authHelper.stopAuthentication();
+ authHelper = null;
+ }
+ authInProgress.set(false);
+ result.success(true);
+ } catch (Exception e) {
+ result.success(false);
+ }
+ }
+
+ private void deviceSupportsBiometrics(final Result result) {
+ result.success(hasBiometricHardware());
+ }
+
+ /*
+ * Returns enrolled biometric types available on device.
+ */
+ private void getEnrolledBiometrics(final Result result) {
+ try {
+ if (activity == null || activity.isFinishing()) {
+ result.error("no_activity", "local_auth plugin requires a foreground activity", null);
+ return;
+ }
+ ArrayList biometrics = getEnrolledBiometrics();
+ result.success(biometrics);
+ } catch (Exception e) {
+ result.error("no_biometrics_available", e.getMessage(), null);
}
}
+ @VisibleForTesting
+ public ArrayList getEnrolledBiometrics() {
+ ArrayList biometrics = new ArrayList<>();
+ if (activity == null || activity.isFinishing()) {
+ return biometrics;
+ }
+ if (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ == BiometricManager.BIOMETRIC_SUCCESS) {
+ biometrics.add("weak");
+ }
+ if (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
+ == BiometricManager.BIOMETRIC_SUCCESS) {
+ biometrics.add("strong");
+ }
+ return biometrics;
+ }
+
@VisibleForTesting
public boolean isDeviceSecure() {
if (keyguardManager == null) return false;
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && keyguardManager.isDeviceSecure());
}
+ @VisibleForTesting
+ public boolean isDeviceSupported() {
+ return isDeviceSecure() || canAuthenticateWithBiometrics();
+ }
+
private boolean canAuthenticateWithBiometrics() {
if (biometricManager == null) return false;
return biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
@@ -222,16 +289,19 @@ public class LocalAuthPlugin implements FlutterPlugin, ActivityAware, LocalAuthA
== BiometricManager.BIOMETRIC_SUCCESS;
}
- @Override
- public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
- LocalAuthApi.setup(binding.getBinaryMessenger(), this);
+ private void isDeviceSupported(Result result) {
+ result.success(isDeviceSupported());
}
@Override
- public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
- LocalAuthApi.setup(binding.getBinaryMessenger(), null);
+ public void onAttachedToEngine(FlutterPluginBinding binding) {
+ channel = new MethodChannel(binding.getFlutterEngine().getDartExecutor(), CHANNEL_NAME);
+ channel.setMethodCallHandler(this);
}
+ @Override
+ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
+
private void setServicesFromActivity(Activity activity) {
if (activity == null) return;
this.activity = activity;
@@ -241,10 +311,11 @@ public class LocalAuthPlugin implements FlutterPlugin, ActivityAware, LocalAuthA
}
@Override
- public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
+ public void onAttachedToActivity(ActivityPluginBinding binding) {
binding.addActivityResultListener(resultListener);
setServicesFromActivity(binding.getActivity());
lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding);
+ channel.setMethodCallHandler(this);
}
@Override
@@ -254,7 +325,7 @@ public class LocalAuthPlugin implements FlutterPlugin, ActivityAware, LocalAuthA
}
@Override
- public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
+ public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
binding.addActivityResultListener(resultListener);
setServicesFromActivity(binding.getActivity());
lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding);
@@ -263,6 +334,7 @@ public class LocalAuthPlugin implements FlutterPlugin, ActivityAware, LocalAuthA
@Override
public void onDetachedFromActivity() {
lifecycle = null;
+ channel.setMethodCallHandler(null);
activity = null;
}
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
deleted file mode 100644
index e878c481c273d56fee6d6bff01b54c410b1d757d..0000000000000000000000000000000000000000
--- a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/Messages.java
+++ /dev/null
@@ -1,748 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-// Autogenerated from Pigeon (v11.0.0), do not edit directly.
-// See also: https://pub.dev/packages/pigeon
-
-package io.flutter.plugins.localauth;
-
-import android.util.Log;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import io.flutter.plugin.common.BasicMessageChannel;
-import io.flutter.plugin.common.BinaryMessenger;
-import io.flutter.plugin.common.MessageCodec;
-import io.flutter.plugin.common.StandardMessageCodec;
-import java.io.ByteArrayOutputStream;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-
-/** Generated class from Pigeon. */
-@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"})
-public class Messages {
-
- /** Error class for passing custom error details to Flutter via a thrown PlatformException. */
- public static class FlutterError extends RuntimeException {
-
- /** The error code. */
- public final String code;
-
- /** The error details. Must be a datatype supported by the api codec. */
- public final Object details;
-
- public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) {
- super(message);
- this.code = code;
- this.details = details;
- }
- }
-
- @NonNull
- protected static ArrayList