[local_auth] Fixed crash on API<28 while invoking authenticateWithBiometrics (#1659)

Add better error handling for when too many tries locks out the fingerprint feature.
diff --git a/packages/local_auth/CHANGELOG.md b/packages/local_auth/CHANGELOG.md
index 3921db7..9832440 100644
--- a/packages/local_auth/CHANGELOG.md
+++ b/packages/local_auth/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.5.1
+* Fix crash on Android versions earlier than 28.
+* [`authenticateWithBiometrics`](https://pub.dev/documentation/local_auth/latest/local_auth/LocalAuthentication/authenticateWithBiometrics.html) will not return result unless Biometric Dialog is closed.
+* Added two more error codes `LockedOut` and `PermanentlyLockedOut`.
+
 ## 0.5.0
  * **Breaking change**. Update the Android API to use androidx Biometric package. This gives
    the prompt the updated Material look. However, it also requires the activity to be a
diff --git a/packages/local_auth/README.md b/packages/local_auth/README.md
index 89eeb9d..34e43f6 100644
--- a/packages/local_auth/README.md
+++ b/packages/local_auth/README.md
@@ -93,8 +93,8 @@
 
 ### Exceptions
 
-There are 4 types of exceptions: PasscodeNotSet, NotEnrolled, NotAvailable and
-OtherOperatingSystem. They are wrapped in LocalAuthenticationError class. You can
+There are 6 types of exceptions: PasscodeNotSet, NotEnrolled, NotAvailable, OtherOperatingSystem, LockedOut and PermanentlyLockedOut.
+They are wrapped in LocalAuthenticationError class. You can
 catch the exception and handle them by different types. For example:
 
 ```dart
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 de75741..6e58527 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
@@ -1,7 +1,6 @@
 // Copyright 2017 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-
 package io.flutter.plugins.localauth;
 
 import android.annotation.SuppressLint;
@@ -25,6 +24,7 @@
 import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
 import androidx.fragment.app.FragmentActivity;
 import io.flutter.plugin.common.MethodCall;
+import java.util.concurrent.Executors;
 
 /**
  * Authenticates the user with fingerprint and sends corresponding response back to Flutter.
@@ -32,6 +32,7 @@
  * <p>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 {
 
@@ -106,31 +107,37 @@
   /** Start the fingerprint listener. */
   private void start() {
     activity.getApplication().registerActivityLifecycleCallbacks(this);
-    new BiometricPrompt(activity, activity.getMainExecutor(), this).authenticate(promptInfo);
+    new BiometricPrompt(activity, Executors.newSingleThreadExecutor(), this)
+        .authenticate(promptInfo);
   }
 
-  /**
-   * Stops the fingerprint listener.
-   *
-   * @param success If the authentication was successful.
-   */
-  private void stop(boolean success) {
+  /** Stops the fingerprint listener. */
+  private void stop(Boolean success) {
     activity.getApplication().unregisterActivityLifecycleCallbacks(this);
-    if (success) {
-      completionHandler.onSuccess();
-    } else {
-      completionHandler.onFailure();
-    }
+    if (success) completionHandler.onSuccess();
+    else completionHandler.onFailure();
   }
 
+  @SuppressLint("SwitchIntDef")
   @Override
   public void onAuthenticationError(int errorCode, CharSequence errString) {
-    if (activityPaused && isAuthSticky) {
-      return;
+    switch (errorCode) {
+      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.");
+        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)");
+        break;
+      case BiometricPrompt.ERROR_CANCELED:
+        if (activityPaused && isAuthSticky) {
+          return;
+        }
+      default:
     }
-
-    // Either the authentication got cancelled by user or we are not interested
-    // in sticky auth, so return failure.
     stop(false);
   }
 
@@ -140,9 +147,7 @@
   }
 
   @Override
-  public void onAuthenticationFailed() {
-    stop(false);
-  }
+  public void onAuthenticationFailed() {}
 
   /**
    * If the activity is paused, we keep track because fingerprint dialog simply returns "User
@@ -160,8 +165,9 @@
     if (isAuthSticky) {
       activityPaused = false;
       final BiometricPrompt prompt =
-          new BiometricPrompt(activity, activity.getMainExecutor(), this);
-      // When activity is resuming, we cannot show the prompt right away. We need to post it to the UI queue.
+          new BiometricPrompt(activity, Executors.newSingleThreadExecutor(), this);
+      // When activity is resuming, we cannot show the prompt right away. We need to post it to the
+      // UI queue.
       new Handler(Looper.myLooper())
           .postDelayed(
               new Runnable() {
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 06dc7a8..9c5f8bd 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
@@ -17,6 +17,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /** LocalAuthPlugin */
+@SuppressWarnings("deprecation")
 public class LocalAuthPlugin implements MethodCallHandler {
   private final Registrar registrar;
   private final AtomicBoolean authInProgress = new AtomicBoolean(false);
diff --git a/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties
index 019065d..5623933 100644
--- a/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,3 +1,4 @@
+#Thu May 30 07:21:52 NPT 2019
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart
index 6d0d9f7..feeb578 100644
--- a/packages/local_auth/example/lib/main.dart
+++ b/packages/local_auth/example/lib/main.dart
@@ -56,7 +56,7 @@
       authenticated = await auth.authenticateWithBiometrics(
           localizedReason: 'Scan your fingerprint to authenticate',
           useErrorDialogs: true,
-          stickyAuth: false);
+          stickyAuth: true);
     } on PlatformException catch (e) {
       print(e);
     }
diff --git a/packages/local_auth/lib/error_codes.dart b/packages/local_auth/lib/error_codes.dart
index 2d226ed..3f6f298 100644
--- a/packages/local_auth/lib/error_codes.dart
+++ b/packages/local_auth/lib/error_codes.dart
@@ -17,3 +17,10 @@
 
 /// Indicates the device operating system is not iOS or Android.
 const String otherOperatingSystem = 'OtherOperatingSystem';
+
+/// Indicates the API lock out due to too many attempts.
+const String lockedOut = 'LockedOut';
+
+/// Indicates the API being disabled due to too many lock outs.
+/// Strong authentication like PIN/Pattern/Password is required to unlock.
+const String permanentlyLockedOut = 'PermanentlyLockedOut';
diff --git a/packages/local_auth/pubspec.yaml b/packages/local_auth/pubspec.yaml
index 4e420a9..d1bd578 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.5.0
+version: 0.5.1
 
 flutter:
   plugin: