[local_auth] Api to stop authentication (#2111)

diff --git a/packages/local_auth/CHANGELOG.md b/packages/local_auth/CHANGELOG.md
index b69e060..0c54edc 100644
--- a/packages/local_auth/CHANGELOG.md
+++ b/packages/local_auth/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.6.1
+
+* Added ability to stop authentication (For Android).
+
 ## 0.6.0+3
 
 * Remove AndroidX warnings.
diff --git a/packages/local_auth/README.md b/packages/local_auth/README.md
index 9cb56e6..ca2aa49 100644
--- a/packages/local_auth/README.md
+++ b/packages/local_auth/README.md
@@ -91,6 +91,16 @@
 
 ```
 
+If needed, you can manually stop authentication for android:
+
+```dart
+
+void _cancelAuthentication() {
+    localAuth.stopAuthentication();
+}
+
+```
+
 ### Exceptions
 
 There are 6 types of exceptions: PasscodeNotSet, NotEnrolled, NotAvailable, OtherOperatingSystem, LockedOut and PermanentlyLockedOut.
diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java
index a4ef01b..99dab6d 100644
--- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java
+++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java
@@ -63,6 +63,7 @@
   private final boolean isAuthSticky;
   private final UiThreadExecutor uiThreadExecutor;
   private boolean activityPaused = false;
+  private BiometricPrompt biometricPrompt;
 
   public AuthenticationHelper(
       FragmentActivity activity, MethodCall call, AuthCompletionHandler completionHandler) {
@@ -84,7 +85,16 @@
   /** Start the fingerprint listener. */
   public void authenticate() {
     activity.getApplication().registerActivityLifecycleCallbacks(this);
-    new BiometricPrompt(activity, uiThreadExecutor, this).authenticate(promptInfo);
+    biometricPrompt = new BiometricPrompt(activity, uiThreadExecutor, this);
+    biometricPrompt.authenticate(promptInfo);
+  }
+
+  /** Cancels the fingerprint authentication. */
+  public void stopAuthentication() {
+    if (biometricPrompt != null) {
+      biometricPrompt.cancelAuthentication();
+      biometricPrompt = null;
+    }
   }
 
   /** Stops the fingerprint listener. */
diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java
index ae69942..6a8bd9e 100644
--- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java
+++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java
@@ -22,6 +22,7 @@
 public class LocalAuthPlugin implements MethodCallHandler {
   private final Registrar registrar;
   private final AtomicBoolean authInProgress = new AtomicBoolean(false);
+  private AuthenticationHelper authenticationHelper;
 
   /** Plugin registration. */
   public static void registerWith(Registrar registrar) {
@@ -37,7 +38,7 @@
   @Override
   public void onMethodCall(MethodCall call, final Result result) {
     if (call.method.equals("authenticateWithBiometrics")) {
-      if (!authInProgress.compareAndSet(false, true)) {
+      if (authInProgress.get()) {
         // Apps should not invoke another authentication request while one is in progress,
         // so we classify this as an error condition. If we ever find a legitimate use case for
         // this, we can try to cancel the ongoing auth and start a new one but for now, not worth
@@ -59,7 +60,8 @@
             null);
         return;
       }
-      AuthenticationHelper authenticationHelper =
+      authInProgress.set(true);
+      authenticationHelper =
           new AuthenticationHelper(
               (FragmentActivity) activity,
               call,
@@ -112,8 +114,27 @@
       } catch (Exception e) {
         result.error("no_biometrics_available", e.getMessage(), null);
       }
+    } else if (call.method.equals(("stopAuthentication"))) {
+      stopAuthentication(result);
     } else {
       result.notImplemented();
     }
   }
+
+  /*
+   Stops the authentication if in progress.
+  */
+  private void stopAuthentication(Result result) {
+    try {
+      if (authenticationHelper != null && authInProgress.get()) {
+        authenticationHelper.stopAuthentication();
+        authenticationHelper = null;
+        result.success(true);
+        return;
+      }
+      result.success(false);
+    } catch (Exception e) {
+      result.success(false);
+    }
+  }
 }
diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart
index feeb578..26dccb7 100644
--- a/packages/local_auth/example/lib/main.dart
+++ b/packages/local_auth/example/lib/main.dart
@@ -21,6 +21,7 @@
   bool _canCheckBiometrics;
   List<BiometricType> _availableBiometrics;
   String _authorized = 'Not Authorized';
+  bool _isAuthenticating = false;
 
   Future<void> _checkBiometrics() async {
     bool canCheckBiometrics;
@@ -53,20 +54,33 @@
   Future<void> _authenticate() async {
     bool authenticated = false;
     try {
+      setState(() {
+        _isAuthenticating = true;
+        _authorized = 'Authenticating';
+      });
       authenticated = await auth.authenticateWithBiometrics(
           localizedReason: 'Scan your fingerprint to authenticate',
           useErrorDialogs: true,
           stickyAuth: true);
+      setState(() {
+        _isAuthenticating = false;
+        _authorized = 'Authenticating';
+      });
     } on PlatformException catch (e) {
       print(e);
     }
     if (!mounted) return;
 
+    final String message = authenticated ? 'Authorized' : 'Not Authorized';
     setState(() {
-      _authorized = authenticated ? 'Authorized' : 'Not Authorized';
+      _authorized = message;
     });
   }
 
+  void _cancelAuthentication() {
+    auth.stopAuthentication();
+  }
+
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
@@ -91,8 +105,9 @@
                 ),
                 Text('Current State: $_authorized\n'),
                 RaisedButton(
-                  child: const Text('Authenticate'),
-                  onPressed: _authenticate,
+                  child: Text(_isAuthenticating ? 'Cancel' : 'Authenticate'),
+                  onPressed:
+                      _isAuthenticating ? _cancelAuthentication : _authenticate,
                 )
               ])),
     ));
diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart
index df69a12..31c1a41 100644
--- a/packages/local_auth/lib/local_auth.dart
+++ b/packages/local_auth/lib/local_auth.dart
@@ -90,6 +90,18 @@
         'authenticateWithBiometrics', args);
   }
 
+  /// Returns true if auth was cancelled successfully.
+  /// This api only works for Android.
+  /// Returns false if there was some error or no auth in progress.
+  ///
+  /// Returns [Future] bool true or false:
+  Future<bool> stopAuthentication() {
+    if (_platform.isAndroid) {
+      return _channel.invokeMethod<bool>('stopAuthentication');
+    }
+    return Future<bool>.sync(() => true);
+  }
+
   /// Returns true if device is capable of checking biometrics
   ///
   /// Returns a [Future] bool true or false:
diff --git a/packages/local_auth/pubspec.yaml b/packages/local_auth/pubspec.yaml
index 38ed72d..13d9f7b 100644
--- a/packages/local_auth/pubspec.yaml
+++ b/packages/local_auth/pubspec.yaml
@@ -3,7 +3,7 @@
   such as Fingerprint Reader and Touch ID.
 author: Flutter Team <flutter-dev@googlegroups.com>
 homepage: https://github.com/flutter/plugins/tree/master/packages/local_auth
-version: 0.6.0+3
+version: 0.6.1
 
 flutter:
   plugin:
@@ -16,7 +16,7 @@
     sdk: flutter
   meta: ^1.0.5
   intl: ">=0.15.1 <0.17.0"
-  platform: ^2.0.0 
+  platform: ^2.0.0
 
 dev_dependencies:
   flutter_test: