diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md index 92a44356cd9..7452cd4d043 100644 --- a/packages/local_auth/local_auth_android/CHANGELOG.md +++ b/packages/local_auth/local_auth_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.23 + +* Switches internals to Pigeon and fixes Java warnings. + ## 1.0.22 * Sets an explicit Java compatibility version. diff --git a/packages/local_auth/local_auth_android/android/lint-baseline.xml b/packages/local_auth/local_auth_android/android/lint-baseline.xml index 3f0a47a6824..a695371a999 100644 --- a/packages/local_auth/local_auth_android/android/lint-baseline.xml +++ b/packages/local_auth/local_auth_android/android/lint-baseline.xml @@ -1,50 +1,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 63f75079e00..ef323b4281c 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,5 +1,4 @@ - 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 c30f879d2c7..b5b824500dc 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,7 +8,6 @@ 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; @@ -26,7 +25,6 @@ import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; -import io.flutter.plugin.common.MethodCall; import java.util.concurrent.Executor; /** @@ -35,35 +33,20 @@ *

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 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); + /** Called when authentication attempt is complete. */ + void complete(Messages.AuthResult authResult); } // This is null when not using v2 embedding; private final Lifecycle lifecycle; private final FragmentActivity activity; private final AuthCompletionHandler completionHandler; - private final MethodCall call; + private final boolean useErrorDialogs; + private final Messages.AuthStrings strings; private final BiometricPrompt.PromptInfo promptInfo; private final boolean isAuthSticky; private final UiThreadExecutor uiThreadExecutor; @@ -73,23 +56,24 @@ interface AuthCompletionHandler { AuthenticationHelper( Lifecycle lifecycle, FragmentActivity activity, - MethodCall call, - AuthCompletionHandler completionHandler, + @NonNull Messages.AuthOptions options, + @NonNull Messages.AuthStrings strings, + @NonNull AuthCompletionHandler completionHandler, boolean allowCredentials) { this.lifecycle = lifecycle; this.activity = activity; this.completionHandler = completionHandler; - this.call = call; - this.isAuthSticky = call.argument("stickyAuth"); + this.strings = strings; + this.isAuthSticky = options.getSticky(); + this.useErrorDialogs = options.getUseErrorDialgs(); this.uiThreadExecutor = new UiThreadExecutor(); BiometricPrompt.PromptInfo.Builder promptBuilder = new BiometricPrompt.PromptInfo.Builder() - .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")); + .setDescription(strings.getReason()) + .setTitle(strings.getSignInTitle()) + .setSubtitle(strings.getBiometricHint()) + .setConfirmationRequired(options.getSensitiveTransaction()); int allowedAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK @@ -98,7 +82,7 @@ interface AuthCompletionHandler { if (allowCredentials) { allowedAuthenticators |= BiometricManager.Authenticators.DEVICE_CREDENTIAL; } else { - promptBuilder.setNegativeButtonText((String) call.argument("cancelButton")); + promptBuilder.setNegativeButtonText(strings.getCancelButton()); } promptBuilder.setAllowedAuthenticators(allowedAuthenticators); @@ -135,40 +119,35 @@ private void stop() { @SuppressLint("SwitchIntDef") @Override - public void onAuthenticationError(int errorCode, CharSequence errString) { + public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { switch (errorCode) { case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL: - if (call.argument("useErrorDialogs")) { + if (useErrorDialogs) { showGoToSettingsDialog( - (String) call.argument("deviceCredentialsRequired"), - (String) call.argument("deviceCredentialsSetupDescription")); + strings.getDeviceCredentialsRequiredTitle(), + strings.getDeviceCredentialsSetupDescription()); return; } - completionHandler.onError("NotAvailable", "Security credentials not available."); + completionHandler.complete(Messages.AuthResult.ERROR_NOT_AVAILABLE); break; case BiometricPrompt.ERROR_NO_SPACE: case BiometricPrompt.ERROR_NO_BIOMETRICS: - if (call.argument("useErrorDialogs")) { + if (useErrorDialogs) { showGoToSettingsDialog( - (String) call.argument("biometricRequired"), - (String) call.argument("goToSettingDescription")); + strings.getBiometricRequiredTitle(), strings.getGoToSettingsDescription()); return; } - completionHandler.onError("NotEnrolled", "No Biometrics enrolled on this device."); + completionHandler.complete(Messages.AuthResult.ERROR_NOT_ENROLLED); break; case BiometricPrompt.ERROR_HW_UNAVAILABLE: case BiometricPrompt.ERROR_HW_NOT_PRESENT: - completionHandler.onError("NotAvailable", "Security credentials not available."); + completionHandler.complete(Messages.AuthResult.ERROR_NOT_AVAILABLE); break; case BiometricPrompt.ERROR_LOCKOUT: - 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."); + completionHandler.complete(Messages.AuthResult.ERROR_LOCKED_OUT_TEMPORARILY); break; case BiometricPrompt.ERROR_LOCKOUT_PERMANENT: - 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)"); + completionHandler.complete(Messages.AuthResult.ERROR_LOCKED_OUT_PERMANENTLY); break; case BiometricPrompt.ERROR_CANCELED: // If we are doing sticky auth and the activity has been paused, @@ -176,18 +155,18 @@ public void onAuthenticationError(int errorCode, CharSequence errString) { if (activityPaused && isAuthSticky) { return; } else { - completionHandler.onFailure(); + completionHandler.complete(Messages.AuthResult.SUCCESS); } break; default: - completionHandler.onFailure(); + completionHandler.complete(Messages.AuthResult.FAILURE); } stop(); } @Override - public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) { - completionHandler.onSuccess(); + public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { + completionHandler.complete(Messages.AuthResult.SUCCESS); stop(); } @@ -212,13 +191,7 @@ public void onActivityResumed(Activity ignored) { 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( - new Runnable() { - @Override - public void run() { - prompt.authenticate(promptInfo); - } - }); + uiThreadExecutor.handler.post(() -> prompt.authenticate(promptInfo)); } } @@ -236,32 +209,26 @@ public void onResume(@NonNull LifecycleOwner owner) { @SuppressLint("InflateParams") private void showGoToSettingsDialog(String title, String descriptionText) { View view = LayoutInflater.from(activity).inflate(R.layout.go_to_setting, null, false); - TextView message = (TextView) view.findViewById(R.id.fingerprint_required); - TextView description = (TextView) view.findViewById(R.id.go_to_setting_description); + 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 = - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - completionHandler.onFailure(); - stop(); - activity.startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS)); - } + (dialog, which) -> { + completionHandler.complete(Messages.AuthResult.FAILURE); + stop(); + activity.startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS)); }; OnClickListener cancelHandler = - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - completionHandler.onFailure(); - stop(); - } + (dialog, which) -> { + completionHandler.complete(Messages.AuthResult.FAILURE); + stop(); }; new AlertDialog.Builder(context) .setView(view) - .setPositiveButton((String) call.argument("goToSetting"), goToSettingHandler) - .setNegativeButton((String) call.argument("cancelButton"), cancelHandler) + .setPositiveButton(strings.getGoToSettingsButton(), goToSettingHandler) + .setNegativeButton(strings.getCancelButton(), cancelHandler) .setCancelable(false) .show(); } @@ -295,7 +262,7 @@ public void onStart(@NonNull LifecycleOwner owner) {} @Override public void onCreate(@NonNull LifecycleOwner owner) {} - private static class UiThreadExecutor implements Executor { + 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 d724b38c0a7..f955f7821ec 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,13 +21,17 @@ 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.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.AuthResultWrapper; +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; /** @@ -35,9 +39,7 @@ * *

Instantiate this in an add to app scenario to gracefully handle activity and context changes. */ -@SuppressWarnings("deprecation") -public class LocalAuthPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware { - private static final String CHANNEL_NAME = "plugins.flutter.io/local_auth_android"; +public class LocalAuthPlugin implements FlutterPlugin, ActivityAware, LocalAuthApi { private static final int LOCK_REQUEST_CODE = 221; private Activity activity; private AuthenticationHelper authHelper; @@ -45,20 +47,19 @@ public class LocalAuthPlugin implements MethodCallHandler, FlutterPlugin, Activi @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; - private Result lockRequestResult; + 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) { - authenticateSuccess(lockRequestResult); + onAuthenticationCompleted(lockRequestResult, AuthResult.SUCCESS); } else { - authenticateFail(lockRequestResult); + onAuthenticationCompleted(lockRequestResult, AuthResult.FAILURE); } lockRequestResult = null; } @@ -72,16 +73,13 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent data) { *

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 attaches this plugin's {@link - * io.flutter.plugin.common.MethodChannel.MethodCallHandler} to the registrar's {@link - * io.flutter.plugin.common.BinaryMessenger}. + * @param registrar provides access to necessary plugin context. */ @SuppressWarnings("deprecation") - public static void registerWith(PluginRegistry.Registrar registrar) { - final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); + public static void registerWith(@NonNull PluginRegistry.Registrar registrar) { final LocalAuthPlugin plugin = new LocalAuthPlugin(); plugin.activity = registrar.activity(); - channel.setMethodCallHandler(plugin); + LocalAuthApi.setup(registrar.messenger(), plugin); registrar.addActivityResultListener(plugin.resultListener); } @@ -92,176 +90,112 @@ public static void registerWith(PluginRegistry.Registrar registrar) { */ public LocalAuthPlugin() {} - @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 @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(AuthClassification.WEAK); } + if (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) + == BiometricManager.BIOMETRIC_SUCCESS) { + biometrics.add(AuthClassification.STRONG); + } + return biometrics; } - /* - * Starts authentication process - */ - private void authenticate(MethodCall call, final Result result) { + public @NonNull Boolean stopAuthentication() { + try { + if (authHelper != null && authInProgress.get()) { + authHelper.stopAuthentication(); + authHelper = null; + } + authInProgress.set(false); + return true; + } catch (Exception e) { + return false; + } + } + + public void authenticate( + @NonNull AuthOptions options, + @NonNull AuthStrings strings, + @NonNull Result result) { if (authInProgress.get()) { - result.error("auth_in_progress", "Authentication in progress", null); + result.success( + new AuthResultWrapper.Builder().setValue(AuthResult.ERROR_ALREADY_IN_PROGRESS).build()); return; } if (activity == null || activity.isFinishing()) { - result.error("no_activity", "local_auth plugin requires a foreground activity", null); + result.success( + new AuthResultWrapper.Builder().setValue(AuthResult.ERROR_NO_ACTIVITY).build()); return; } if (!(activity instanceof FragmentActivity)) { - result.error( - "no_fragment_activity", - "local_auth plugin requires activity to be a FragmentActivity.", - null); + result.success( + new AuthResultWrapper.Builder().setValue(AuthResult.ERROR_NOT_FRAGMENT_ACTIVITY).build()); return; } if (!isDeviceSupported()) { - authInProgress.set(false); - result.error("NotAvailable", "Required security features not enabled", null); + result.success( + new AuthResultWrapper.Builder().setValue(AuthResult.ERROR_NOT_AVAILABLE).build()); return; } authInProgress.set(true); AuthCompletionHandler completionHandler = createAuthCompletionHandler(result); - boolean isBiometricOnly = call.argument("biometricOnly"); - boolean allowCredentials = !isBiometricOnly && canAuthenticateWithDeviceCredential(); + boolean allowCredentials = !options.getBiometricOnly() && canAuthenticateWithDeviceCredential(); - sendAuthenticationRequest(call, completionHandler, allowCredentials); - return; + sendAuthenticationRequest(options, strings, allowCredentials, completionHandler); } @VisibleForTesting - 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); - } - } - }; + public @NonNull AuthCompletionHandler createAuthCompletionHandler( + @NonNull final Result result) { + return authResult -> onAuthenticationCompleted(result, authResult); } @VisibleForTesting public void sendAuthenticationRequest( - MethodCall call, AuthCompletionHandler completionHandler, boolean allowCredentials) { + @NonNull AuthOptions options, + @NonNull AuthStrings strings, + boolean allowCredentials, + @NonNull AuthCompletionHandler completionHandler) { authHelper = new AuthenticationHelper( - lifecycle, (FragmentActivity) activity, call, completionHandler, allowCredentials); + lifecycle, + (FragmentActivity) activity, + options, + strings, + completionHandler, + allowCredentials); authHelper.authenticate(); } - private void authenticateSuccess(Result result) { + void onAuthenticationCompleted(Result result, AuthResult value) { if (authInProgress.compareAndSet(true, false)) { - result.success(true); + result.success(new AuthResultWrapper.Builder().setValue(value).build()); } } - 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) @@ -288,18 +222,15 @@ public boolean canAuthenticateWithDeviceCredential() { == BiometricManager.BIOMETRIC_SUCCESS; } - private void isDeviceSupported(Result result) { - result.success(isDeviceSupported()); - } - @Override - public void onAttachedToEngine(FlutterPluginBinding binding) { - channel = new MethodChannel(binding.getFlutterEngine().getDartExecutor(), CHANNEL_NAME); - channel.setMethodCallHandler(this); + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + LocalAuthApi.setup(binding.getBinaryMessenger(), this); } @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {} + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + LocalAuthApi.setup(binding.getBinaryMessenger(), null); + } private void setServicesFromActivity(Activity activity) { if (activity == null) return; @@ -310,11 +241,10 @@ private void setServicesFromActivity(Activity activity) { } @Override - public void onAttachedToActivity(ActivityPluginBinding binding) { + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { binding.addActivityResultListener(resultListener); setServicesFromActivity(binding.getActivity()); lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); - channel.setMethodCallHandler(this); } @Override @@ -324,7 +254,7 @@ public void onDetachedFromActivityForConfigChanges() { } @Override - public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { binding.addActivityResultListener(resultListener); setServicesFromActivity(binding.getActivity()); lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); @@ -333,7 +263,6 @@ public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding @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 new file mode 100644 index 00000000000..a8b6738bd75 --- /dev/null +++ b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/Messages.java @@ -0,0 +1,742 @@ +// 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 (v9.2.4), 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 wrapError(@NonNull Throwable exception) { + ArrayList errorList = new ArrayList(3); + if (exception instanceof FlutterError) { + FlutterError error = (FlutterError) exception; + errorList.add(error.code); + errorList.add(error.getMessage()); + errorList.add(error.details); + } else { + errorList.add(exception.toString()); + errorList.add(exception.getClass().getSimpleName()); + errorList.add( + "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); + } + return errorList; + } + + /** Possible outcomes of an authentication attempt. */ + public enum AuthResult { + /** The user authenticated successfully. */ + SUCCESS(0), + /** The user failed to successfully authenticate. */ + FAILURE(1), + /** An authentication was already in progress. */ + ERROR_ALREADY_IN_PROGRESS(2), + /** There is no foreground activity. */ + ERROR_NO_ACTIVITY(3), + /** The foreground activity is not a FragmentActivity. */ + ERROR_NOT_FRAGMENT_ACTIVITY(4), + /** The authentication system was not available. */ + ERROR_NOT_AVAILABLE(5), + /** No biometrics are enrolled. */ + ERROR_NOT_ENROLLED(6), + /** The user is locked out temporarily due to too many failed attempts. */ + ERROR_LOCKED_OUT_TEMPORARILY(7), + /** The user is locked out until they log in another way due to too many failed attempts. */ + ERROR_LOCKED_OUT_PERMANENTLY(8); + + final int index; + + private AuthResult(final int index) { + this.index = index; + } + } + + /** Pigeon equivalent of the subset of BiometricType used by Android. */ + public enum AuthClassification { + WEAK(0), + STRONG(1); + + final int index; + + private AuthClassification(final int index) { + this.index = index; + } + } + + /** + * Pigeon version of AndroidAuthStrings, plus the authorization reason. + * + *

See auth_messages_android.dart for details. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class AuthStrings { + private @NonNull String reason; + + public @NonNull String getReason() { + return reason; + } + + public void setReason(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"reason\" is null."); + } + this.reason = setterArg; + } + + private @NonNull String biometricHint; + + public @NonNull String getBiometricHint() { + return biometricHint; + } + + public void setBiometricHint(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"biometricHint\" is null."); + } + this.biometricHint = setterArg; + } + + private @NonNull String biometricNotRecognized; + + public @NonNull String getBiometricNotRecognized() { + return biometricNotRecognized; + } + + public void setBiometricNotRecognized(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"biometricNotRecognized\" is null."); + } + this.biometricNotRecognized = setterArg; + } + + private @NonNull String biometricRequiredTitle; + + public @NonNull String getBiometricRequiredTitle() { + return biometricRequiredTitle; + } + + public void setBiometricRequiredTitle(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"biometricRequiredTitle\" is null."); + } + this.biometricRequiredTitle = setterArg; + } + + private @NonNull String cancelButton; + + public @NonNull String getCancelButton() { + return cancelButton; + } + + public void setCancelButton(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"cancelButton\" is null."); + } + this.cancelButton = setterArg; + } + + private @NonNull String deviceCredentialsRequiredTitle; + + public @NonNull String getDeviceCredentialsRequiredTitle() { + return deviceCredentialsRequiredTitle; + } + + public void setDeviceCredentialsRequiredTitle(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException( + "Nonnull field \"deviceCredentialsRequiredTitle\" is null."); + } + this.deviceCredentialsRequiredTitle = setterArg; + } + + private @NonNull String deviceCredentialsSetupDescription; + + public @NonNull String getDeviceCredentialsSetupDescription() { + return deviceCredentialsSetupDescription; + } + + public void setDeviceCredentialsSetupDescription(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException( + "Nonnull field \"deviceCredentialsSetupDescription\" is null."); + } + this.deviceCredentialsSetupDescription = setterArg; + } + + private @NonNull String goToSettingsButton; + + public @NonNull String getGoToSettingsButton() { + return goToSettingsButton; + } + + public void setGoToSettingsButton(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"goToSettingsButton\" is null."); + } + this.goToSettingsButton = setterArg; + } + + private @NonNull String goToSettingsDescription; + + public @NonNull String getGoToSettingsDescription() { + return goToSettingsDescription; + } + + public void setGoToSettingsDescription(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"goToSettingsDescription\" is null."); + } + this.goToSettingsDescription = setterArg; + } + + private @NonNull String signInTitle; + + public @NonNull String getSignInTitle() { + return signInTitle; + } + + public void setSignInTitle(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"signInTitle\" is null."); + } + this.signInTitle = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + AuthStrings() {} + + public static final class Builder { + + private @Nullable String reason; + + public @NonNull Builder setReason(@NonNull String setterArg) { + this.reason = setterArg; + return this; + } + + private @Nullable String biometricHint; + + public @NonNull Builder setBiometricHint(@NonNull String setterArg) { + this.biometricHint = setterArg; + return this; + } + + private @Nullable String biometricNotRecognized; + + public @NonNull Builder setBiometricNotRecognized(@NonNull String setterArg) { + this.biometricNotRecognized = setterArg; + return this; + } + + private @Nullable String biometricRequiredTitle; + + public @NonNull Builder setBiometricRequiredTitle(@NonNull String setterArg) { + this.biometricRequiredTitle = setterArg; + return this; + } + + private @Nullable String cancelButton; + + public @NonNull Builder setCancelButton(@NonNull String setterArg) { + this.cancelButton = setterArg; + return this; + } + + private @Nullable String deviceCredentialsRequiredTitle; + + public @NonNull Builder setDeviceCredentialsRequiredTitle(@NonNull String setterArg) { + this.deviceCredentialsRequiredTitle = setterArg; + return this; + } + + private @Nullable String deviceCredentialsSetupDescription; + + public @NonNull Builder setDeviceCredentialsSetupDescription(@NonNull String setterArg) { + this.deviceCredentialsSetupDescription = setterArg; + return this; + } + + private @Nullable String goToSettingsButton; + + public @NonNull Builder setGoToSettingsButton(@NonNull String setterArg) { + this.goToSettingsButton = setterArg; + return this; + } + + private @Nullable String goToSettingsDescription; + + public @NonNull Builder setGoToSettingsDescription(@NonNull String setterArg) { + this.goToSettingsDescription = setterArg; + return this; + } + + private @Nullable String signInTitle; + + public @NonNull Builder setSignInTitle(@NonNull String setterArg) { + this.signInTitle = setterArg; + return this; + } + + public @NonNull AuthStrings build() { + AuthStrings pigeonReturn = new AuthStrings(); + pigeonReturn.setReason(reason); + pigeonReturn.setBiometricHint(biometricHint); + pigeonReturn.setBiometricNotRecognized(biometricNotRecognized); + pigeonReturn.setBiometricRequiredTitle(biometricRequiredTitle); + pigeonReturn.setCancelButton(cancelButton); + pigeonReturn.setDeviceCredentialsRequiredTitle(deviceCredentialsRequiredTitle); + pigeonReturn.setDeviceCredentialsSetupDescription(deviceCredentialsSetupDescription); + pigeonReturn.setGoToSettingsButton(goToSettingsButton); + pigeonReturn.setGoToSettingsDescription(goToSettingsDescription); + pigeonReturn.setSignInTitle(signInTitle); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(10); + toListResult.add(reason); + toListResult.add(biometricHint); + toListResult.add(biometricNotRecognized); + toListResult.add(biometricRequiredTitle); + toListResult.add(cancelButton); + toListResult.add(deviceCredentialsRequiredTitle); + toListResult.add(deviceCredentialsSetupDescription); + toListResult.add(goToSettingsButton); + toListResult.add(goToSettingsDescription); + toListResult.add(signInTitle); + return toListResult; + } + + static @NonNull AuthStrings fromList(@NonNull ArrayList list) { + AuthStrings pigeonResult = new AuthStrings(); + Object reason = list.get(0); + pigeonResult.setReason((String) reason); + Object biometricHint = list.get(1); + pigeonResult.setBiometricHint((String) biometricHint); + Object biometricNotRecognized = list.get(2); + pigeonResult.setBiometricNotRecognized((String) biometricNotRecognized); + Object biometricRequiredTitle = list.get(3); + pigeonResult.setBiometricRequiredTitle((String) biometricRequiredTitle); + Object cancelButton = list.get(4); + pigeonResult.setCancelButton((String) cancelButton); + Object deviceCredentialsRequiredTitle = list.get(5); + pigeonResult.setDeviceCredentialsRequiredTitle((String) deviceCredentialsRequiredTitle); + Object deviceCredentialsSetupDescription = list.get(6); + pigeonResult.setDeviceCredentialsSetupDescription((String) deviceCredentialsSetupDescription); + Object goToSettingsButton = list.get(7); + pigeonResult.setGoToSettingsButton((String) goToSettingsButton); + Object goToSettingsDescription = list.get(8); + pigeonResult.setGoToSettingsDescription((String) goToSettingsDescription); + Object signInTitle = list.get(9); + pigeonResult.setSignInTitle((String) signInTitle); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class AuthOptions { + private @NonNull Boolean biometricOnly; + + public @NonNull Boolean getBiometricOnly() { + return biometricOnly; + } + + public void setBiometricOnly(@NonNull Boolean setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"biometricOnly\" is null."); + } + this.biometricOnly = setterArg; + } + + private @NonNull Boolean sensitiveTransaction; + + public @NonNull Boolean getSensitiveTransaction() { + return sensitiveTransaction; + } + + public void setSensitiveTransaction(@NonNull Boolean setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"sensitiveTransaction\" is null."); + } + this.sensitiveTransaction = setterArg; + } + + private @NonNull Boolean sticky; + + public @NonNull Boolean getSticky() { + return sticky; + } + + public void setSticky(@NonNull Boolean setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"sticky\" is null."); + } + this.sticky = setterArg; + } + + private @NonNull Boolean useErrorDialgs; + + public @NonNull Boolean getUseErrorDialgs() { + return useErrorDialgs; + } + + public void setUseErrorDialgs(@NonNull Boolean setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"useErrorDialgs\" is null."); + } + this.useErrorDialgs = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + AuthOptions() {} + + public static final class Builder { + + private @Nullable Boolean biometricOnly; + + public @NonNull Builder setBiometricOnly(@NonNull Boolean setterArg) { + this.biometricOnly = setterArg; + return this; + } + + private @Nullable Boolean sensitiveTransaction; + + public @NonNull Builder setSensitiveTransaction(@NonNull Boolean setterArg) { + this.sensitiveTransaction = setterArg; + return this; + } + + private @Nullable Boolean sticky; + + public @NonNull Builder setSticky(@NonNull Boolean setterArg) { + this.sticky = setterArg; + return this; + } + + private @Nullable Boolean useErrorDialgs; + + public @NonNull Builder setUseErrorDialgs(@NonNull Boolean setterArg) { + this.useErrorDialgs = setterArg; + return this; + } + + public @NonNull AuthOptions build() { + AuthOptions pigeonReturn = new AuthOptions(); + pigeonReturn.setBiometricOnly(biometricOnly); + pigeonReturn.setSensitiveTransaction(sensitiveTransaction); + pigeonReturn.setSticky(sticky); + pigeonReturn.setUseErrorDialgs(useErrorDialgs); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(4); + toListResult.add(biometricOnly); + toListResult.add(sensitiveTransaction); + toListResult.add(sticky); + toListResult.add(useErrorDialgs); + return toListResult; + } + + static @NonNull AuthOptions fromList(@NonNull ArrayList list) { + AuthOptions pigeonResult = new AuthOptions(); + Object biometricOnly = list.get(0); + pigeonResult.setBiometricOnly((Boolean) biometricOnly); + Object sensitiveTransaction = list.get(1); + pigeonResult.setSensitiveTransaction((Boolean) sensitiveTransaction); + Object sticky = list.get(2); + pigeonResult.setSticky((Boolean) sticky); + Object useErrorDialgs = list.get(3); + pigeonResult.setUseErrorDialgs((Boolean) useErrorDialgs); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class AuthResultWrapper { + private @NonNull AuthResult value; + + public @NonNull AuthResult getValue() { + return value; + } + + public void setValue(@NonNull AuthResult setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"value\" is null."); + } + this.value = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + AuthResultWrapper() {} + + public static final class Builder { + + private @Nullable AuthResult value; + + public @NonNull Builder setValue(@NonNull AuthResult setterArg) { + this.value = setterArg; + return this; + } + + public @NonNull AuthResultWrapper build() { + AuthResultWrapper pigeonReturn = new AuthResultWrapper(); + pigeonReturn.setValue(value); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(value == null ? null : value.index); + return toListResult; + } + + static @NonNull AuthResultWrapper fromList(@NonNull ArrayList list) { + AuthResultWrapper pigeonResult = new AuthResultWrapper(); + Object value = list.get(0); + pigeonResult.setValue(value == null ? null : AuthResult.values()[(int) value]); + return pigeonResult; + } + } + + public interface Result { + @SuppressWarnings("UnknownNullness") + void success(T result); + + void error(@NonNull Throwable error); + } + + private static class LocalAuthApiCodec extends StandardMessageCodec { + public static final LocalAuthApiCodec INSTANCE = new LocalAuthApiCodec(); + + private LocalAuthApiCodec() {} + + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return AuthOptions.fromList((ArrayList) readValue(buffer)); + case (byte) 129: + return AuthResultWrapper.fromList((ArrayList) readValue(buffer)); + case (byte) 130: + return AuthStrings.fromList((ArrayList) readValue(buffer)); + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof AuthOptions) { + stream.write(128); + writeValue(stream, ((AuthOptions) value).toList()); + } else if (value instanceof AuthResultWrapper) { + stream.write(129); + writeValue(stream, ((AuthResultWrapper) value).toList()); + } else if (value instanceof AuthStrings) { + stream.write(130); + writeValue(stream, ((AuthStrings) value).toList()); + } else { + super.writeValue(stream, value); + } + } + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface LocalAuthApi { + /** Returns true if this device supports authentication. */ + @NonNull + Boolean isDeviceSupported(); + /** + * Returns true if this device can support biometric authentication, whether any biometrics are + * enrolled or not. + */ + @NonNull + Boolean deviceCanSupportBiometrics(); + /** + * Cancels any in-progress authentication. + * + *

Returns true only if authentication was in progress, and was successfully cancelled. + */ + @NonNull + Boolean stopAuthentication(); + /** + * Returns the biometric types that are enrolled, and can thus be used without additional setup. + */ + @NonNull + List getEnrolledBiometrics(); + /** + * Attempts to authenticate the user with the provided [options], and using [strings] for any + * UI. + */ + void authenticate( + @NonNull AuthOptions options, + @NonNull AuthStrings strings, + @NonNull Result result); + + /** The codec used by LocalAuthApi. */ + static @NonNull MessageCodec getCodec() { + return LocalAuthApiCodec.INSTANCE; + } + /** Sets up an instance of `LocalAuthApi` to handle messages through the `binaryMessenger`. */ + static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable LocalAuthApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.LocalAuthApi.isDeviceSupported", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.isDeviceSupported(); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.LocalAuthApi.deviceCanSupportBiometrics", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.deviceCanSupportBiometrics(); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.LocalAuthApi.stopAuthentication", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.stopAuthentication(); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.LocalAuthApi.getEnrolledBiometrics", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + List output = api.getEnrolledBiometrics(); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.LocalAuthApi.authenticate", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + AuthOptions optionsArg = (AuthOptions) args.get(0); + AuthStrings stringsArg = (AuthStrings) args.get(1); + Result resultCallback = + new Result() { + public void success(AuthResultWrapper result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.authenticate(optionsArg, stringsArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } +} diff --git a/packages/local_auth/local_auth_android/android/src/main/res/values/styles.xml b/packages/local_auth/local_auth_android/android/src/main/res/values/styles.xml index 7a0719f4188..ca014754c4a 100644 --- a/packages/local_auth/local_auth_android/android/src/main/res/values/styles.xml +++ b/packages/local_auth/local_auth_android/android/src/main/res/values/styles.xml @@ -1,5 +1,6 @@ +