[local_auth] Add platform interface to prepare for migration to federated architecture (#4697)

diff --git a/packages/local_auth/local_auth_platform_interface/AUTHORS b/packages/local_auth/local_auth_platform_interface/AUTHORS
new file mode 100644
index 0000000..d569469
--- /dev/null
+++ b/packages/local_auth/local_auth_platform_interface/AUTHORS
@@ -0,0 +1,67 @@
+# Below is a list of people and organizations that have contributed
+# to the Flutter project. Names should be added to the list like so:
+#
+#   Name/Organization <email address>
+
+Google Inc.
+The Chromium Authors
+German Saprykin <saprykin.h@gmail.com>
+Benjamin Sauer <sauer.benjamin@gmail.com>
+larsenthomasj@gmail.com
+Ali Bitek <alibitek@protonmail.ch>
+Pol Batlló <pol.batllo@gmail.com>
+Anatoly Pulyaevskiy
+Hayden Flinner <haydenflinner@gmail.com>
+Stefano Rodriguez <hlsroddy@gmail.com>
+Salvatore Giordano <salvatoregiordanoo@gmail.com>
+Brian Armstrong <brian@flutter.institute>
+Paul DeMarco <paulmdemarco@gmail.com>
+Fabricio Nogueira <feufeu@gmail.com>
+Simon Lightfoot <simon@devangels.london>
+Ashton Thomas <ashton@acrinta.com>
+Thomas Danner <thmsdnnr@gmail.com>
+Diego Velásquez <diego.velasquez.lopez@gmail.com>
+Hajime Nakamura <nkmrhj@gmail.com>
+Tuyển Vũ Xuân <netsoft1985@gmail.com>
+Miguel Ruivo <miguel@miguelruivo.com>
+Sarthak Verma <sarthak@artiosys.com>
+Mike Diarmid <mike@invertase.io>
+Invertase <oss@invertase.io>
+Elliot Hesp <elliot@invertase.io>
+Vince Varga <vince.varga@smaho.com>
+Aawaz Gyawali <awazgyawali@gmail.com>
+EUI Limited <ian.evans3@admiralgroup.co.uk>
+Katarina Sheremet <katarina@sheremet.ch>
+Thomas Stockx <thomas@stockxit.com>
+Sarbagya Dhaubanjar <sarbagyastha@gmail.com>
+Ozkan Eksi <ozeksi@gmail.com>
+Rishab Nayak <rishab@bu.edu>
+ko2ic <ko2ic.dev@gmail.com>
+Jonathan Younger <jonathan@daikini.com>
+Jose Sanchez <josesm82@gmail.com>
+Debkanchan Samadder <debu.samadder@gmail.com>
+Audrius Karosevicius <audrius.karosevicius@gmail.com>
+Lukasz Piliszczuk <lukasz@intheloup.io>
+SoundReply Solutions GmbH <ch@soundreply.com>
+Rafal Wachol <rwachol@gmail.com>
+Pau Picas <pau.picas@gmail.com>
+Christian Weder <chrstian.weder@yapeal.ch>
+Alexandru Tuca <salexandru.tuca@outlook.com>
+Christian Weder <chrstian.weder@yapeal.ch>
+Rhodes Davis Jr. <rody.davis.jr@gmail.com>
+Luigi Agosti <luigi@tengio.com>
+Quentin Le Guennec <quentin@tengio.com>
+Koushik Ravikumar <koushik@tengio.com>
+Nissim Dsilva <nissim@tengio.com>
+Giancarlo Rocha <giancarloiff@gmail.com>
+Ryo Miyake <ryo@miyake.id>
+Théo Champion <contact.theochampion@gmail.com>
+Kazuki Yamaguchi <y.kazuki0614n@gmail.com>
+Eitan Schwartz <eshvartz@gmail.com>
+Chris Rutkowski <chrisrutkowski89@gmail.com>
+Juan Alvarez <juan.alvarez@resideo.com>
+Aleksandr Yurkovskiy <sanekyy@gmail.com>
+Anton Borries <mail@antonborri.es>
+Alex Li <google@alexv525.com>
+Rahul Raj <64.rahulraj@gmail.com>
+Bodhi Mulders <info@bemacized.net>
diff --git a/packages/local_auth/local_auth_platform_interface/CHANGELOG.md b/packages/local_auth/local_auth_platform_interface/CHANGELOG.md
new file mode 100644
index 0000000..0d8803f
--- /dev/null
+++ b/packages/local_auth/local_auth_platform_interface/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+* Initial release.
diff --git a/packages/local_auth/local_auth_platform_interface/LICENSE b/packages/local_auth/local_auth_platform_interface/LICENSE
new file mode 100644
index 0000000..c6823b8
--- /dev/null
+++ b/packages/local_auth/local_auth_platform_interface/LICENSE
@@ -0,0 +1,25 @@
+Copyright 2013 The Flutter Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/local_auth/local_auth_platform_interface/README.md b/packages/local_auth/local_auth_platform_interface/README.md
new file mode 100644
index 0000000..3b01ced
--- /dev/null
+++ b/packages/local_auth/local_auth_platform_interface/README.md
@@ -0,0 +1,26 @@
+# local_auth_platform_interface
+
+A common platform interface for the [`local_auth`][1] plugin.
+
+This interface allows platform-specific implementations of the `local_auth`
+plugin, as well as the plugin itself, to ensure they are supporting the
+same interface.
+
+# Usage
+
+To implement a new platform-specific implementation of `local_auth`, extend
+[`LocalAuthPlatform`][2] with an implementation that performs the
+platform-specific behavior, and when you register your plugin, set the default
+`LocalAuthPlatform` by calling
+`LocalAuthPlatform.instance = MyLocalAuthPlatform()`.
+
+# Note on breaking changes
+
+Strongly prefer non-breaking changes (such as adding a method to the interface)
+over breaking changes for this package.
+
+See https://flutter.dev/go/platform-interface-breaking-changes for a discussion
+on why a less-clean interface is preferable to a breaking change.
+
+[1]: ../local_auth
+[2]: lib/local_auth_platform_interface.dart
diff --git a/packages/local_auth/local_auth_platform_interface/lib/default_method_channel_platform.dart b/packages/local_auth/local_auth_platform_interface/lib/default_method_channel_platform.dart
new file mode 100644
index 0000000..c68a3bf
--- /dev/null
+++ b/packages/local_auth/local_auth_platform_interface/lib/default_method_channel_platform.dart
@@ -0,0 +1,78 @@
+// 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.
+
+import 'package:flutter/services.dart';
+import 'package:local_auth_platform_interface/local_auth_platform_interface.dart';
+import 'package:local_auth_platform_interface/types/auth_messages.dart';
+import 'package:local_auth_platform_interface/types/auth_options.dart';
+import 'package:local_auth_platform_interface/types/biometric_type.dart';
+
+const MethodChannel _channel = MethodChannel('plugins.flutter.io/local_auth');
+
+/// The default interface implementation acting as a placeholder for
+/// the native implementation to be set.
+///
+/// This implementation is not used by any of the implementations in this
+/// repository, and exists only for backward compatibility with any
+/// clients that were relying on internal details of the method channel
+/// in the pre-federated plugin.
+class DefaultLocalAuthPlatform extends LocalAuthPlatform {
+  @override
+  Future<bool> authenticate({
+    required String localizedReason,
+    required Iterable<AuthMessages> authMessages,
+    AuthenticationOptions options = const AuthenticationOptions(),
+  }) async {
+    assert(localizedReason.isNotEmpty);
+    final Map<String, Object> args = <String, Object>{
+      'localizedReason': localizedReason,
+      'useErrorDialogs': options.useErrorDialogs,
+      'stickyAuth': options.stickyAuth,
+      'sensitiveTransaction': options.sensitiveTransaction,
+      'biometricOnly': options.biometricOnly,
+    };
+    for (final AuthMessages messages in authMessages) {
+      args.addAll(messages.args);
+    }
+    return (await _channel.invokeMethod<bool>('authenticate', args)) ?? false;
+  }
+
+  @override
+  Future<List<BiometricType>> getEnrolledBiometrics() async {
+    final List<String> result = (await _channel.invokeListMethod<String>(
+          'getAvailableBiometrics',
+        )) ??
+        <String>[];
+    final List<BiometricType> biometrics = <BiometricType>[];
+    for (final String value in result) {
+      switch (value) {
+        case 'face':
+          biometrics.add(BiometricType.face);
+          break;
+        case 'fingerprint':
+          biometrics.add(BiometricType.fingerprint);
+          break;
+        case 'iris':
+          biometrics.add(BiometricType.iris);
+          break;
+        case 'undefined':
+          break;
+      }
+    }
+    return biometrics;
+  }
+
+  @override
+  Future<bool> deviceSupportsBiometrics() async {
+    return (await getEnrolledBiometrics()).isNotEmpty;
+  }
+
+  @override
+  Future<bool> isDeviceSupported() async =>
+      (await _channel.invokeMethod<bool>('isDeviceSupported')) ?? false;
+
+  @override
+  Future<bool> stopAuthentication() async =>
+      await _channel.invokeMethod<bool>('stopAuthentication') ?? false;
+}
diff --git a/packages/local_auth/local_auth_platform_interface/lib/local_auth_platform_interface.dart b/packages/local_auth/local_auth_platform_interface/lib/local_auth_platform_interface.dart
new file mode 100644
index 0000000..b909ee9
--- /dev/null
+++ b/packages/local_auth/local_auth_platform_interface/lib/local_auth_platform_interface.dart
@@ -0,0 +1,99 @@
+// 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.
+
+import 'package:local_auth_platform_interface/default_method_channel_platform.dart';
+import 'package:local_auth_platform_interface/types/auth_messages.dart';
+import 'package:local_auth_platform_interface/types/auth_options.dart';
+import 'package:local_auth_platform_interface/types/biometric_type.dart';
+import 'package:plugin_platform_interface/plugin_platform_interface.dart';
+
+/// The interface that implementations of local_auth must implement.
+///
+/// Platform implementations should extend this class rather than implement it as `local_auth`
+/// does not consider newly added methods to be breaking changes. Extending this class
+/// (using `extends`) ensures that the subclass will get the default implementation, while
+/// platform implementations that `implements` this interface will be broken by newly added
+/// [LocalAuthPlatform] methods.
+abstract class LocalAuthPlatform extends PlatformInterface {
+  /// Constructs a LocalAuthPlatform.
+  LocalAuthPlatform() : super(token: _token);
+
+  static final Object _token = Object();
+
+  static LocalAuthPlatform _instance = DefaultLocalAuthPlatform();
+
+  /// The default instance of [LocalAuthPlatform] to use.
+  ///
+  /// Defaults to [DefaultLocalAuthPlatform].
+  static LocalAuthPlatform get instance => _instance;
+
+  /// Platform-specific implementations should set this with their own
+  /// platform-specific class that extends [LocalAuthPlatform] when they
+  /// register themselves.
+  static set instance(LocalAuthPlatform instance) {
+    PlatformInterface.verifyToken(instance, _token);
+    _instance = instance;
+  }
+
+  /// Authenticates the user with biometrics available on the device while also
+  /// allowing the user to use device authentication - pin, pattern, passcode.
+  ///
+  /// Returns true if the user successfully authenticated, false otherwise.
+  ///
+  /// [localizedReason] is the message to show to user while prompting them
+  /// for authentication. This is typically along the lines of: 'Please scan
+  /// your finger to access MyApp.'. This must not be empty.
+  ///
+  /// Provide [authMessages] if you want to
+  /// customize messages in the dialogs.
+  ///
+  /// Provide [options] for configuring further authentication related options.
+  ///
+  /// Throws a [PlatformException] if there were technical problems with local
+  /// authentication (e.g. lack of relevant hardware). This might throw
+  /// [PlatformException] with error code [otherOperatingSystem] on the iOS
+  /// simulator.
+  Future<bool> authenticate({
+    required String localizedReason,
+    required Iterable<AuthMessages> authMessages,
+    AuthenticationOptions options = const AuthenticationOptions(),
+  }) async {
+    throw UnimplementedError('authenticate() has not been implemented.');
+  }
+
+  /// Returns true if the device is capable of checking biometrics.
+  ///
+  /// This will return true even if there are no biometrics currently enrolled.
+  Future<bool> deviceSupportsBiometrics() async {
+    throw UnimplementedError('canCheckBiometrics() has not been implemented.');
+  }
+
+  /// Returns a list of enrolled biometrics.
+  ///
+  /// Possible values include:
+  /// - BiometricType.face
+  /// - BiometricType.fingerprint
+  /// - BiometricType.iris (not yet implemented)
+  /// - BiometricType.strong
+  /// - BiometricType.weak
+  Future<List<BiometricType>> getEnrolledBiometrics() async {
+    throw UnimplementedError(
+        'getAvailableBiometrics() has not been implemented.');
+  }
+
+  /// Returns true if device is capable of checking biometrics or is able to
+  /// fail over to device credentials.
+  Future<bool> isDeviceSupported() async {
+    throw UnimplementedError('isDeviceSupported() has not been implemented.');
+  }
+
+  /// Cancels any authentication currently in progress.
+  ///
+  /// Returns true if auth was cancelled successfully.
+  /// Returns false if there was no authentication in progress,
+  /// or an error occurred.
+  Future<bool> stopAuthentication() async {
+    throw UnimplementedError('stopAuthentication() has not been implemented.');
+  }
+}
diff --git a/packages/local_auth/local_auth_platform_interface/lib/types/auth_messages.dart b/packages/local_auth/local_auth_platform_interface/lib/types/auth_messages.dart
new file mode 100644
index 0000000..d51980d
--- /dev/null
+++ b/packages/local_auth/local_auth_platform_interface/lib/types/auth_messages.dart
@@ -0,0 +1,12 @@
+// 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.
+
+/// Abstract class for storing platform specific strings.
+abstract class AuthMessages {
+  /// Constructs an instance of [AuthMessages].
+  const AuthMessages();
+
+  /// Returns all platform-specific messages as a map.
+  Map<String, String> get args;
+}
diff --git a/packages/local_auth/local_auth_platform_interface/lib/types/auth_options.dart b/packages/local_auth/local_auth_platform_interface/lib/types/auth_options.dart
new file mode 100644
index 0000000..c4b646c
--- /dev/null
+++ b/packages/local_auth/local_auth_platform_interface/lib/types/auth_options.dart
@@ -0,0 +1,60 @@
+// 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.
+
+import 'package:flutter/foundation.dart';
+
+/// Options wrapper for [LocalAuthPlatform.authenticate] parameters.
+@immutable
+class AuthenticationOptions {
+  /// Constructs a new instance.
+  const AuthenticationOptions({
+    this.useErrorDialogs = true,
+    this.stickyAuth = false,
+    this.sensitiveTransaction = true,
+    this.biometricOnly = false,
+  });
+
+  /// Whether the system will attempt to handle user-fixable issues encountered
+  /// while authenticating. For instance, if a fingerprint reader exists on the
+  /// device but there's no fingerprint registered, the plugin might attempt to
+  /// take the user to settings to add one. Anything that is not user fixable,
+  /// such as no biometric sensor on device, will still result in
+  /// a [PlatformException].
+  final bool useErrorDialogs;
+
+  /// Used when the application goes into background for any reason while the
+  /// authentication is in progress. Due to security reasons, the
+  /// authentication has to be stopped at that time. If stickyAuth is set to
+  /// true, authentication resumes when the app is resumed. If it is set to
+  /// false (default), then as soon as app is paused a failure message is sent
+  /// back to Dart and it is up to the client app to restart authentication or
+  /// do something else.
+  final bool stickyAuth;
+
+  /// Whether platform specific precautions are enabled. For instance, on face
+  /// unlock, Android opens a confirmation dialog after the face is recognized
+  /// to make sure the user meant to unlock their device.
+  final bool sensitiveTransaction;
+
+  /// Prevent authentications from using non-biometric local authentication
+  /// such as pin, passcode, or pattern.
+  final bool biometricOnly;
+
+  @override
+  bool operator ==(Object other) =>
+      identical(this, other) ||
+      other is AuthenticationOptions &&
+          runtimeType == other.runtimeType &&
+          useErrorDialogs == other.useErrorDialogs &&
+          stickyAuth == other.stickyAuth &&
+          sensitiveTransaction == other.sensitiveTransaction &&
+          biometricOnly == other.biometricOnly;
+
+  @override
+  int get hashCode =>
+      useErrorDialogs.hashCode ^
+      stickyAuth.hashCode ^
+      sensitiveTransaction.hashCode ^
+      biometricOnly.hashCode;
+}
diff --git a/packages/local_auth/local_auth_platform_interface/lib/types/biometric_type.dart b/packages/local_auth/local_auth_platform_interface/lib/types/biometric_type.dart
new file mode 100644
index 0000000..9c335e2
--- /dev/null
+++ b/packages/local_auth/local_auth_platform_interface/lib/types/biometric_type.dart
@@ -0,0 +1,27 @@
+// 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.
+
+/// Various types of biometric authentication.
+/// Some platforms report specific biometric types, while others report only
+/// classifications like strong and weak.
+enum BiometricType {
+  /// Face authentication.
+  face,
+
+  /// Fingerprint authentication.
+  fingerprint,
+
+  /// Iris authentication.
+  iris,
+
+  /// Any biometric (e.g. fingerprint, iris, or face) on the device that the
+  /// platform API considers to be strong. For example, on Android this
+  /// corresponds to Class 3.
+  strong,
+
+  /// Any biometric (e.g. fingerprint, iris, or face) on the device that the
+  /// platform API considers to be weak. For example, on Android this
+  /// corresponds to Class 2.
+  weak,
+}
diff --git a/packages/local_auth/local_auth_platform_interface/pubspec.yaml b/packages/local_auth/local_auth_platform_interface/pubspec.yaml
new file mode 100644
index 0000000..f042689
--- /dev/null
+++ b/packages/local_auth/local_auth_platform_interface/pubspec.yaml
@@ -0,0 +1,22 @@
+name: local_auth_platform_interface
+description: A common platform interface for the local_auth plugin.
+repository: https://github.com/flutter/plugins/tree/master/packages/local_auth/local_auth_platform_interface
+issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22
+# NOTE: We strongly prefer non-breaking changes, even at the expense of a
+# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
+version: 1.0.0
+
+environment:
+  sdk: ">=2.14.0 <3.0.0"
+  flutter: ">=2.8.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+  intl: ^0.17.0
+  plugin_platform_interface: ^2.1.2
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  mockito: ^5.0.0
\ No newline at end of file
diff --git a/packages/local_auth/local_auth_platform_interface/test/default_method_channel_platform_test.dart b/packages/local_auth/local_auth_platform_interface/test/default_method_channel_platform_test.dart
new file mode 100644
index 0000000..3853fd8
--- /dev/null
+++ b/packages/local_auth/local_auth_platform_interface/test/default_method_channel_platform_test.dart
@@ -0,0 +1,180 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:local_auth_platform_interface/default_method_channel_platform.dart';
+import 'package:local_auth_platform_interface/local_auth_platform_interface.dart';
+import 'package:local_auth_platform_interface/types/auth_messages.dart';
+import 'package:local_auth_platform_interface/types/auth_options.dart';
+import 'package:local_auth_platform_interface/types/biometric_type.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  const MethodChannel channel = MethodChannel(
+    'plugins.flutter.io/local_auth',
+  );
+
+  final List<MethodCall> log = <MethodCall>[];
+  late LocalAuthPlatform localAuthentication;
+
+  test(
+      'DefaultLocalAuthPlatform is registered as the default platform implementation',
+      () async {
+    expect(LocalAuthPlatform.instance,
+        const TypeMatcher<DefaultLocalAuthPlatform>());
+  });
+
+  test('getAvailableBiometrics', () async {
+    channel.setMockMethodCallHandler((MethodCall methodCall) {
+      log.add(methodCall);
+      return Future<dynamic>.value(<BiometricType>[]);
+    });
+    localAuthentication = DefaultLocalAuthPlatform();
+    log.clear();
+    await localAuthentication.getEnrolledBiometrics();
+    expect(
+      log,
+      <Matcher>[
+        isMethodCall('getAvailableBiometrics', arguments: null),
+      ],
+    );
+  });
+
+  group('Boolean returning methods', () {
+    setUp(() {
+      channel.setMockMethodCallHandler((MethodCall methodCall) {
+        log.add(methodCall);
+        return Future<dynamic>.value(true);
+      });
+      localAuthentication = DefaultLocalAuthPlatform();
+      log.clear();
+    });
+
+    test('isDeviceSupported', () async {
+      await localAuthentication.isDeviceSupported();
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('isDeviceSupported', arguments: null),
+        ],
+      );
+    });
+
+    test('stopAuthentication', () async {
+      await localAuthentication.stopAuthentication();
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('stopAuthentication', arguments: null),
+        ],
+      );
+    });
+
+    group('authenticate with device auth fail over', () {
+      test('authenticate with no args.', () async {
+        await localAuthentication.authenticate(
+          authMessages: <AuthMessages>[],
+          localizedReason: 'Needs secure',
+          options: const AuthenticationOptions(biometricOnly: true),
+        );
+        expect(
+          log,
+          <Matcher>[
+            isMethodCall(
+              'authenticate',
+              arguments: <String, dynamic>{
+                'localizedReason': 'Needs secure',
+                'useErrorDialogs': true,
+                'stickyAuth': false,
+                'sensitiveTransaction': true,
+                'biometricOnly': true,
+              },
+            ),
+          ],
+        );
+      });
+
+      test('authenticate with no sensitive transaction.', () async {
+        await localAuthentication.authenticate(
+          authMessages: <AuthMessages>[],
+          localizedReason: 'Insecure',
+          options: const AuthenticationOptions(
+            sensitiveTransaction: false,
+            useErrorDialogs: false,
+            biometricOnly: true,
+          ),
+        );
+        expect(
+          log,
+          <Matcher>[
+            isMethodCall(
+              'authenticate',
+              arguments: <String, dynamic>{
+                'localizedReason': 'Insecure',
+                'useErrorDialogs': false,
+                'stickyAuth': false,
+                'sensitiveTransaction': false,
+                'biometricOnly': true,
+              },
+            ),
+          ],
+        );
+      });
+    });
+
+    group('authenticate with biometrics only', () {
+      test('authenticate with no args.', () async {
+        await localAuthentication.authenticate(
+          authMessages: <AuthMessages>[],
+          localizedReason: 'Needs secure',
+        );
+        expect(
+          log,
+          <Matcher>[
+            isMethodCall(
+              'authenticate',
+              arguments: <String, dynamic>{
+                'localizedReason': 'Needs secure',
+                'useErrorDialogs': true,
+                'stickyAuth': false,
+                'sensitiveTransaction': true,
+                'biometricOnly': false,
+              },
+            ),
+          ],
+        );
+      });
+
+      test('authenticate with no sensitive transaction.', () async {
+        await localAuthentication.authenticate(
+          authMessages: <AuthMessages>[],
+          localizedReason: 'Insecure',
+          options: const AuthenticationOptions(
+            sensitiveTransaction: false,
+            useErrorDialogs: false,
+          ),
+        );
+        expect(
+          log,
+          <Matcher>[
+            isMethodCall(
+              'authenticate',
+              arguments: <String, dynamic>{
+                'localizedReason': 'Insecure',
+                'useErrorDialogs': false,
+                'stickyAuth': false,
+                'sensitiveTransaction': false,
+                'biometricOnly': false,
+              },
+            ),
+          ],
+        );
+      });
+    });
+  });
+}