Implementations of biometric contraints for weak and convenience tiers
(1) 24 hours fallback
(2) 4 hours idle timeout

Bug: 141025588

Test: atest AuthServiceTest
Test: atest KeyguardUpdateMonitorTest
Test: atest BiometricsUnlockControllerTest
Test: atest KeyguardIndicationControllerTest
Test: make -j
Change-Id: I1078ce39a2ae1e4c250b6468e477b703e3016e2c
diff --git a/core/java/android/app/trust/IStrongAuthTracker.aidl b/core/java/android/app/trust/IStrongAuthTracker.aidl
index 36c71bf..6d54396 100644
--- a/core/java/android/app/trust/IStrongAuthTracker.aidl
+++ b/core/java/android/app/trust/IStrongAuthTracker.aidl
@@ -23,4 +23,5 @@
  */
 oneway interface IStrongAuthTracker {
     void onStrongAuthRequiredChanged(int strongAuthRequired, int userId);
+    void onIsNonStrongBiometricAllowedChanged(boolean allowed, int userId);
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 3a0660d..9b9889e 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -97,8 +97,10 @@
         }
 
         @Override // binder call
-        public void onAuthenticationSucceeded(long deviceId, Face face, int userId) {
-            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, face).sendToTarget();
+        public void onAuthenticationSucceeded(long deviceId, Face face, int userId,
+                boolean isStrongBiometric) {
+            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0,
+                    face).sendToTarget();
         }
 
         @Override // binder call
@@ -814,6 +816,7 @@
         private Face mFace;
         private CryptoObject mCryptoObject;
         private int mUserId;
+        private boolean mIsStrongBiometric;
 
         /**
          * Authentication result
@@ -822,10 +825,12 @@
          * @param face   the recognized face data, if allowed.
          * @hide
          */
-        public AuthenticationResult(CryptoObject crypto, Face face, int userId) {
+        public AuthenticationResult(CryptoObject crypto, Face face, int userId,
+                boolean isStrongBiometric) {
             mCryptoObject = crypto;
             mFace = face;
             mUserId = userId;
+            mIsStrongBiometric = isStrongBiometric;
         }
 
         /**
@@ -857,6 +862,16 @@
         public int getUserId() {
             return mUserId;
         }
+
+        /**
+         * Check whether the strength of the face modality associated with this operation is strong
+         * (i.e. not weak or convenience).
+         *
+         * @hide
+         */
+        public boolean isStrongBiometric() {
+            return mIsStrongBiometric;
+        }
     }
 
     /**
@@ -1056,7 +1071,8 @@
                             msg.arg2 /* vendorCode */);
                     break;
                 case MSG_AUTHENTICATION_SUCCEEDED:
-                    sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */);
+                    sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
+                            msg.arg2 == 1 /* isStrongBiometric */);
                     break;
                 case MSG_AUTHENTICATION_FAILED:
                     sendAuthenticatedFailed();
@@ -1133,10 +1149,10 @@
         }
     }
 
-    private void sendAuthenticatedSucceeded(Face face, int userId) {
+    private void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {
         if (mAuthenticationCallback != null) {
             final AuthenticationResult result =
-                    new AuthenticationResult(mCryptoObject, face, userId);
+                    new AuthenticationResult(mCryptoObject, face, userId, isStrongBiometric);
             mAuthenticationCallback.onAuthenticationSucceeded(result);
         }
     }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 8ba2473..6318274 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -110,4 +110,7 @@
     void getFeature(int userId, int feature, IFaceServiceReceiver receiver, String opPackageName);
 
     void userActivity();
+
+    // Initialize the OEM configured biometric strength
+    void initConfiguredStrength(int strength);
 }
diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
index 10f9c43..7582308 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -24,7 +24,8 @@
 oneway interface IFaceServiceReceiver {
     void onEnrollResult(long deviceId, int faceId, int remaining);
     void onAcquired(long deviceId, int acquiredInfo, int vendorCode);
-    void onAuthenticationSucceeded(long deviceId, in Face face, int userId);
+    void onAuthenticationSucceeded(long deviceId, in Face face, int userId,
+            boolean isStrongBiometric);
     void onAuthenticationFailed(long deviceId);
     void onError(long deviceId, int error, int vendorCode);
     void onRemoved(long deviceId, int faceId, int remaining);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index f301a5c..dc8ea5e 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -176,6 +176,7 @@
         private Fingerprint mFingerprint;
         private CryptoObject mCryptoObject;
         private int mUserId;
+        private boolean mIsStrongBiometric;
 
         /**
          * Authentication result
@@ -184,10 +185,12 @@
          * @param fingerprint the recognized fingerprint data, if allowed.
          * @hide
          */
-        public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId) {
+        public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId,
+                boolean isStrongBiometric) {
             mCryptoObject = crypto;
             mFingerprint = fingerprint;
             mUserId = userId;
+            mIsStrongBiometric = isStrongBiometric;
         }
 
         /**
@@ -211,6 +214,15 @@
          * @hide
          */
         public int getUserId() { return mUserId; }
+
+        /**
+         * Check whether the strength of the fingerprint modality associated with this operation is
+         * strong (i.e. not weak or convenience).
+         * @hide
+         */
+        public boolean isStrongBiometric() {
+            return mIsStrongBiometric;
+        }
     };
 
     /**
@@ -833,7 +845,8 @@
                             msg.arg2 /* vendorCode */);
                     break;
                 case MSG_AUTHENTICATION_SUCCEEDED:
-                    sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */);
+                    sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */,
+                            msg.arg2 == 1 /* isStrongBiometric */);
                     break;
                 case MSG_AUTHENTICATION_FAILED:
                     sendAuthenticatedFailed();
@@ -890,10 +903,10 @@
         }
     }
 
-    private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) {
+    private void sendAuthenticatedSucceeded(Fingerprint fp, int userId, boolean isStrongBiometric) {
         if (mAuthenticationCallback != null) {
             final AuthenticationResult result =
-                    new AuthenticationResult(mCryptoObject, fp, userId);
+                    new AuthenticationResult(mCryptoObject, fp, userId, isStrongBiometric);
             mAuthenticationCallback.onAuthenticationSucceeded(result);
         }
     }
@@ -1078,8 +1091,10 @@
         }
 
         @Override // binder call
-        public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) {
-            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget();
+        public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId,
+                boolean isStrongBiometric) {
+            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0,
+                    fp).sendToTarget();
         }
 
         @Override // binder call
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index f2ffd08d..0285448 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -113,4 +113,7 @@
 
     // Removes a callback set by addClientActiveCallback
     void removeClientActiveCallback(IFingerprintClientActiveCallback callback);
+
+    // Initialize the OEM configured biometric strength
+    void initConfiguredStrength(int strength);
 }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index cf1c94e..4412cee 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -24,7 +24,8 @@
 oneway interface IFingerprintServiceReceiver {
     void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining);
     void onAcquired(long deviceId, int acquiredInfo, int vendorCode);
-    void onAuthenticationSucceeded(long deviceId, in Fingerprint fp, int userId);
+    void onAuthenticationSucceeded(long deviceId, in Fingerprint fp, int userId,
+            boolean isStrongBiometric);
     void onAuthenticationFailed(long deviceId);
     void onError(long deviceId, int error, int vendorCode);
     void onRemoved(long deviceId, int fingerId, int groupId, int remaining);
diff --git a/core/java/android/hardware/iris/IIrisService.aidl b/core/java/android/hardware/iris/IIrisService.aidl
index 8cf3c13..5ef0a0c 100644
--- a/core/java/android/hardware/iris/IIrisService.aidl
+++ b/core/java/android/hardware/iris/IIrisService.aidl
@@ -21,4 +21,6 @@
  * @hide
  */
 interface IIrisService {
-}
\ No newline at end of file
+    // Initialize the OEM configured biometric strength
+    void initConfiguredStrength(int strength);
+}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index e24e982..e35fda1 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -57,6 +57,8 @@
     void registerStrongAuthTracker(in IStrongAuthTracker tracker);
     void unregisterStrongAuthTracker(in IStrongAuthTracker tracker);
     void requireStrongAuth(int strongAuthReason, int userId);
+    void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId);
+    void scheduleNonStrongBiometricIdleTimeout(int userId);
     void systemReady();
     void userPresent(int userId);
     int getStrongAuthForUser(int userId);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 864429c..d9b2902 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -49,6 +49,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 
@@ -1382,6 +1383,22 @@
         }
     }
 
+    public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
+        try {
+            getLockSettings().reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not report successful biometric unlock", e);
+        }
+    }
+
+    public void scheduleNonStrongBiometricIdleTimeout(int userId) {
+        try {
+            getLockSettings().scheduleNonStrongBiometricIdleTimeout(userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not schedule non-strong biometric idle timeout", e);
+        }
+    }
+
     /**
      * @see StrongAuthTracker#getStrongAuthForUser
      */
@@ -1557,7 +1574,8 @@
                         SOME_AUTH_REQUIRED_AFTER_USER_REQUEST,
                         STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
                         STRONG_AUTH_REQUIRED_AFTER_TIMEOUT,
-                        STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN})
+                        STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
+                        STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT})
         @Retention(RetentionPolicy.SOURCE)
         public @interface StrongAuthFlags {}
 
@@ -1604,6 +1622,12 @@
         public static final int STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE = 0x40;
 
         /**
+         * Strong authentication is required because it hasn't been used for a time after a
+         * non-strong biometric (i.e. weak or convenience biometric) is used to unlock device.
+         */
+        public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80;
+
+        /**
          * Strong auth flags that do not prevent biometric methods from being accepted as auth.
          * If any other flags are set, biometric authentication is disabled.
          */
@@ -1614,6 +1638,10 @@
         private final H mHandler;
         private final int mDefaultStrongAuthFlags;
 
+        private final SparseBooleanArray mIsNonStrongBiometricAllowedForUser =
+                new SparseBooleanArray();
+        private final boolean mDefaultIsNonStrongBiometricAllowed = true;
+
         public StrongAuthTracker(Context context) {
             this(context, Looper.myLooper());
         }
@@ -1657,8 +1685,21 @@
          * @return true if unlocking with a biometric method alone is allowed for {@code userId}
          * by the current strong authentication requirements.
          */
-        public boolean isBiometricAllowedForUser(int userId) {
-            return (getStrongAuthForUser(userId) & ~ALLOWING_BIOMETRIC) == 0;
+        public boolean isBiometricAllowedForUser(boolean isStrongBiometric, int userId) {
+            boolean allowed = ((getStrongAuthForUser(userId) & ~ALLOWING_BIOMETRIC) == 0);
+            if (!isStrongBiometric) {
+                allowed &= isNonStrongBiometricAllowedAfterIdleTimeout(userId);
+            }
+            return allowed;
+        }
+
+        /**
+         * @return true if unlocking with a non-strong (i.e. weak or convenience) biometric method
+         * alone is allowed for {@code userId}, otherwise returns false.
+         */
+        public boolean isNonStrongBiometricAllowedAfterIdleTimeout(int userId) {
+            return mIsNonStrongBiometricAllowedForUser.get(userId,
+                    mDefaultIsNonStrongBiometricAllowed);
         }
 
         /**
@@ -1667,6 +1708,12 @@
         public void onStrongAuthRequiredChanged(int userId) {
         }
 
+        /**
+         * Called when whether non-strong biometric is allowed for {@code userId} changed.
+         */
+        public void onIsNonStrongBiometricAllowedChanged(int userId) {
+        }
+
         protected void handleStrongAuthRequiredChanged(@StrongAuthFlags int strongAuthFlags,
                 int userId) {
             int oldValue = getStrongAuthForUser(userId);
@@ -1680,6 +1727,18 @@
             }
         }
 
+        protected void handleIsNonStrongBiometricAllowedChanged(boolean allowed,
+                int userId) {
+            boolean oldValue = isNonStrongBiometricAllowedAfterIdleTimeout(userId);
+            if (allowed != oldValue) {
+                if (allowed == mDefaultIsNonStrongBiometricAllowed) {
+                    mIsNonStrongBiometricAllowedForUser.delete(userId);
+                } else {
+                    mIsNonStrongBiometricAllowedForUser.put(userId, allowed);
+                }
+                onIsNonStrongBiometricAllowedChanged(userId);
+            }
+        }
 
         protected final IStrongAuthTracker.Stub mStub = new IStrongAuthTracker.Stub() {
             @Override
@@ -1688,10 +1747,17 @@
                 mHandler.obtainMessage(H.MSG_ON_STRONG_AUTH_REQUIRED_CHANGED,
                         strongAuthFlags, userId).sendToTarget();
             }
+
+            @Override
+            public void onIsNonStrongBiometricAllowedChanged(boolean allowed, int userId) {
+                mHandler.obtainMessage(H.MSG_ON_IS_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED,
+                        allowed ? 1 : 0, userId).sendToTarget();
+            }
         };
 
         private class H extends Handler {
             static final int MSG_ON_STRONG_AUTH_REQUIRED_CHANGED = 1;
+            static final int MSG_ON_IS_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED = 2;
 
             public H(Looper looper) {
                 super(looper);
@@ -1703,6 +1769,10 @@
                     case MSG_ON_STRONG_AUTH_REQUIRED_CHANGED:
                         handleStrongAuthRequiredChanged(msg.arg1, msg.arg2);
                         break;
+                    case MSG_ON_IS_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED:
+                        handleIsNonStrongBiometricAllowedChanged(msg.arg1 == 1 /* allowed */,
+                                msg.arg2);
+                        break;
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 09d4d5f..20b1e0d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -57,6 +57,12 @@
     int PROMPT_REASON_PREPARE_FOR_UPDATE = 6;
 
     /**
+     * Primary auth is required because the user uses weak/convenience biometrics and hasn't used
+     * primary auth since a while
+     */
+    int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
+
+    /**
      * Interface back to keyguard to tell it when security
      * @param callback
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9ba3860..52ea300 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -80,6 +80,7 @@
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -91,6 +92,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -108,6 +110,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.TimeZone;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -264,6 +267,7 @@
     // If the user long pressed the lock icon, disabling face auth for the current session.
     private boolean mLockIconPressed;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private final Executor mBackgroundExecutor;
 
     /**
      * Short delay before restarting biometric authentication after a successful try
@@ -320,12 +324,22 @@
                 }
             };
 
+    private class BiometricAuthenticated {
+        private final boolean mAuthenticated;
+        private final boolean mIsStrongBiometric;
+
+        BiometricAuthenticated(boolean authenticated, boolean isStrongBiometric) {
+            this.mAuthenticated = authenticated;
+            this.mIsStrongBiometric = isStrongBiometric;
+        }
+    }
+
     private SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray();
     private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
     private SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
     private SparseBooleanArray mUserTrustIsUsuallyManaged = new SparseBooleanArray();
-    private SparseBooleanArray mUserFingerprintAuthenticated = new SparseBooleanArray();
-    private SparseBooleanArray mUserFaceAuthenticated = new SparseBooleanArray();
+    private SparseArray<BiometricAuthenticated> mUserFingerprintAuthenticated = new SparseArray<>();
+    private SparseArray<BiometricAuthenticated> mUserFaceAuthenticated = new SparseArray<>();
     private SparseBooleanArray mUserFaceUnlockRunning = new SparseBooleanArray();
     private Map<Integer, Intent> mSecondaryLockscreenRequirement = new HashMap<Integer, Intent>();
 
@@ -523,10 +537,11 @@
     }
 
     @VisibleForTesting
-    protected void onFingerprintAuthenticated(int userId) {
+    protected void onFingerprintAuthenticated(int userId, boolean isStrongBiometric) {
         Assert.isMainThread();
         Trace.beginSection("KeyGuardUpdateMonitor#onFingerPrintAuthenticated");
-        mUserFingerprintAuthenticated.put(userId, true);
+        mUserFingerprintAuthenticated.put(userId,
+                new BiometricAuthenticated(true, isStrongBiometric));
         // Update/refresh trust state only if user can skip bouncer
         if (getUserCanSkipBouncer(userId)) {
             mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FINGERPRINT);
@@ -536,7 +551,8 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT);
+                cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT,
+                        isStrongBiometric);
             }
         }
 
@@ -546,9 +562,21 @@
         // Only authenticate fingerprint once when assistant is visible
         mAssistantVisible = false;
 
+        // Report unlock with strong or non-strong biometric
+        reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+
         Trace.endSection();
     }
 
+    private void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
+        mBackgroundExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                mLockPatternUtils.reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+            }
+        });
+    }
+
     private void handleFingerprintAuthFailed() {
         Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -574,7 +602,7 @@
         }
     }
 
-    private void handleFingerprintAuthenticated(int authUserId) {
+    private void handleFingerprintAuthenticated(int authUserId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
         try {
             final int userId;
@@ -592,7 +620,7 @@
                 Log.d(TAG, "Fingerprint disabled by DPM for userId: " + userId);
                 return;
             }
-            onFingerprintAuthenticated(userId);
+            onFingerprintAuthenticated(userId, isStrongBiometric);
         } finally {
             setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
         }
@@ -683,10 +711,11 @@
     }
 
     @VisibleForTesting
-    protected void onFaceAuthenticated(int userId) {
+    protected void onFaceAuthenticated(int userId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated");
         Assert.isMainThread();
-        mUserFaceAuthenticated.put(userId, true);
+        mUserFaceAuthenticated.put(userId,
+                new BiometricAuthenticated(true, isStrongBiometric));
         // Update/refresh trust state only if user can skip bouncer
         if (getUserCanSkipBouncer(userId)) {
             mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);
@@ -697,7 +726,8 @@
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
                 cb.onBiometricAuthenticated(userId,
-                        BiometricSourceType.FACE);
+                        BiometricSourceType.FACE,
+                        isStrongBiometric);
             }
         }
 
@@ -707,6 +737,9 @@
         // Only authenticate face once when assistant is visible
         mAssistantVisible = false;
 
+        // Report unlock with strong or non-strong biometric
+        reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+
         Trace.endSection();
     }
 
@@ -737,7 +770,7 @@
         }
     }
 
-    private void handleFaceAuthenticated(int authUserId) {
+    private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
         try {
             if (mGoingToSleep) {
@@ -760,7 +793,7 @@
                 return;
             }
             if (DEBUG_FACE) Log.d(TAG, "Face auth succeeded for user " + userId);
-            onFaceAuthenticated(userId);
+            onFaceAuthenticated(userId, isStrongBiometric);
         } finally {
             setFaceRunningState(BIOMETRIC_STATE_STOPPED);
         }
@@ -914,9 +947,13 @@
      * Returns whether the user is unlocked with biometrics.
      */
     public boolean getUserUnlockedWithBiometric(int userId) {
-        boolean fingerprintOrFace = mUserFingerprintAuthenticated.get(userId)
-                || mUserFaceAuthenticated.get(userId);
-        return fingerprintOrFace && isUnlockingWithBiometricAllowed();
+        BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
+        BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
+        boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated
+                && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric);
+        boolean faceAllowed = face != null && face.mAuthenticated
+                && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric);
+        return fingerprintAllowed || faceAllowed;
     }
 
     public boolean getUserTrustIsManaged(int userId) {
@@ -970,8 +1007,8 @@
         return mUserTrustIsUsuallyManaged.get(userId);
     }
 
-    public boolean isUnlockingWithBiometricAllowed() {
-        return mStrongAuthTracker.isUnlockingWithBiometricAllowed();
+    public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
+        return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric);
     }
 
     public boolean isUserInLockdown(int userId) {
@@ -1169,7 +1206,7 @@
         @Override
         public void onAuthenticationSucceeded(AuthenticationResult result) {
             Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
-            handleFingerprintAuthenticated(result.getUserId());
+            handleFingerprintAuthenticated(result.getUserId(), result.isStrongBiometric());
             Trace.endSection();
         }
 
@@ -1201,7 +1238,7 @@
         @Override
         public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {
             Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
-            handleFaceAuthenticated(result.getUserId());
+            handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
             Trace.endSection();
         }
 
@@ -1305,9 +1342,9 @@
             mStrongAuthRequiredChangedCallback = strongAuthRequiredChangedCallback;
         }
 
-        public boolean isUnlockingWithBiometricAllowed() {
+        public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
             int userId = getCurrentUser();
-            return isBiometricAllowedForUser(userId);
+            return isBiometricAllowedForUser(isStrongBiometric, userId);
         }
 
         public boolean hasUserAuthenticatedSinceBoot() {
@@ -1438,12 +1475,14 @@
             Context context,
             @Main Looper mainLooper,
             BroadcastDispatcher broadcastDispatcher,
-            DumpController dumpController) {
+            DumpController dumpController,
+            @Background Executor backgroundExecutor) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
         mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
         dumpController.registerDumpable(this);
+        mBackgroundExecutor = backgroundExecutor;
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -1753,14 +1792,16 @@
     }
 
     private boolean shouldListenForFingerprintAssistant() {
+        BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(getCurrentUser());
         return mAssistantVisible && mKeyguardOccluded
-                && !mUserFingerprintAuthenticated.get(getCurrentUser(), false)
+                && !(fingerprint != null && fingerprint.mAuthenticated)
                 && !mUserHasTrust.get(getCurrentUser(), false);
     }
 
     private boolean shouldListenForFaceAssistant() {
+        BiometricAuthenticated face = mUserFaceAuthenticated.get(getCurrentUser());
         return mAssistantVisible && mKeyguardOccluded
-                && !mUserFaceAuthenticated.get(getCurrentUser(), false)
+                && !(face != null && face.mAuthenticated)
                 && !mUserHasTrust.get(getCurrentUser(), false);
     }
 
@@ -1817,7 +1858,7 @@
     public void onLockIconPressed() {
         mLockIconPressed = true;
         final int userId = getCurrentUser();
-        mUserFaceAuthenticated.put(userId, false);
+        mUserFaceAuthenticated.put(userId, null);
         updateFaceListeningState();
         mStrongAuthTracker.onStrongAuthRequiredChanged(userId);
     }
@@ -2691,9 +2732,11 @@
         if (mFpm != null && mFpm.isHardwareDetected()) {
             final int userId = ActivityManager.getCurrentUser();
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
+            BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
             pw.println("  Fingerprint state (user=" + userId + ")");
-            pw.println("    allowed=" + isUnlockingWithBiometricAllowed());
-            pw.println("    auth'd=" + mUserFingerprintAuthenticated.get(userId));
+            pw.println("    allowed=" + fingerprint != null
+                    && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric));
+            pw.println("    auth'd=" + fingerprint != null && fingerprint.mAuthenticated);
             pw.println("    authSinceBoot="
                     + getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
             pw.println("    disabled(DPM)=" + isFingerprintDisabled(userId));
@@ -2706,9 +2749,11 @@
         if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
             final int userId = ActivityManager.getCurrentUser();
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
+            BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
             pw.println("  Face authentication state (user=" + userId + ")");
-            pw.println("    allowed=" + isUnlockingWithBiometricAllowed());
-            pw.println("    auth'd=" + mUserFaceAuthenticated.get(userId));
+            pw.println("    allowed=" + face != null
+                    && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric));
+            pw.println("    auth'd=" + face != null && face.mAuthenticated);
             pw.println("    authSinceBoot="
                     + getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
             pw.println("    disabled(DPM)=" + isFaceDisabled(userId));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 49f72a9..12e0ecd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -239,7 +239,8 @@
      * @param userId the user id for which the biometric sample was authenticated
      * @param biometricSourceType
      */
-    public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) { }
+    public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+            boolean isStrongBiometric) { }
 
     /**
      * Called when biometric authentication provides help string (e.g. "Try again")
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 1a47dac..dc0cb03 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -96,6 +96,7 @@
     private void fakeWakeAndUnlock() {
         mBiometricUnlockController.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
         mBiometricUnlockController.onBiometricAuthenticated(
-                KeyguardUpdateMonitor.getCurrentUser(), BiometricSourceType.FINGERPRINT);
+                KeyguardUpdateMonitor.getCurrentUser(), BiometricSourceType.FINGERPRINT,
+                true /* isStrongBiometric */);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index 6a64c83..f719cc6 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -130,7 +130,8 @@
             new KeyguardUpdateMonitorCallback() {
                 @Override
                 public void onBiometricAuthenticated(int userId,
-                        BiometricSourceType biometricSourceType) {
+                        BiometricSourceType biometricSourceType,
+                        boolean isStrongBiometric) {
                     if (userId == KeyguardUpdateMonitor.getCurrentUser()
                             && biometricSourceType == BiometricSourceType.FACE) {
                         mJustUnlockedWithFace = true;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
index 2f3e336..a084ae6 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
@@ -78,7 +78,8 @@
             new KeyguardUpdateMonitorCallback() {
                 @Override
                 public void onBiometricAuthenticated(int userId,
-                        BiometricSourceType biometricSourceType) {
+                        BiometricSourceType biometricSourceType,
+                        boolean isStrongBiometric) {
                     if (userId == KeyguardUpdateMonitor.getCurrentUser()
                             && biometricSourceType == BiometricSourceType.FACE) {
                         mJustUnlockedWithFace = true;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b1db7df..9577121 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -22,6 +22,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
@@ -537,7 +538,8 @@
         }
 
         @Override
-        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+                boolean isStrongBiometric) {
             if (mLockPatternUtils.isSecure(userId)) {
                 mLockPatternUtils.getDevicePolicyManager().reportSuccessfulBiometricAttempt(
                         userId);
@@ -673,6 +675,9 @@
                 return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
             } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
+            } else if (any && (strongAuth
+                    & STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT) != 0) {
+                return KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT;
             }
             return KeyguardSecurityView.PROMPT_REASON_NONE;
         }
@@ -1839,6 +1844,13 @@
             mShowKeyguardWakeLock.release();
         }
         mKeyguardDisplayManager.show();
+
+        // schedule 4hr idle timeout after which non-strong biometrics (i.e. weak or convenience
+        // biometric) can't be used to unlock device until unlocking with strong biometric or
+        // primary auth (i.e. PIN/pattern/password)
+        mLockPatternUtils.scheduleNonStrongBiometricIdleTimeout(
+                KeyguardUpdateMonitor.getCurrentUser());
+
         Trace.endSection();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 7d3d406..4f8e6cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -645,7 +645,13 @@
         @Override
         public void onBiometricHelp(int msgId, String helpString,
                 BiometricSourceType biometricSourceType) {
-            if (!mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()) {
+            // TODO(b/141025588): refactor to reduce repetition of code/comments
+            // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+            // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
+            // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+            // check of whether non-strong biometric is allowed
+            if (!mKeyguardUpdateMonitor
+                    .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) {
                 return;
             }
             boolean showSwipeToUnlock =
@@ -705,13 +711,21 @@
 
         private boolean shouldSuppressFingerprintError(int msgId,
                 KeyguardUpdateMonitor updateMonitor) {
-            return ((!updateMonitor.isUnlockingWithBiometricAllowed()
+            // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+            // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
+            // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+            // check of whether non-strong biometric is allowed
+            return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
                     && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
                     || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED);
         }
 
         private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
-            return ((!updateMonitor.isUnlockingWithBiometricAllowed()
+            // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+            // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
+            // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+            // check of whether non-strong biometric is allowed
+            return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
                     && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
                     || msgId == FaceManager.FACE_ERROR_CANCELED);
         }
@@ -745,8 +759,9 @@
         }
 
         @Override
-        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
-            super.onBiometricAuthenticated(userId, biometricSourceType);
+        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+                boolean isStrongBiometric) {
+            super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric);
             mHandler.sendEmptyMessage(MSG_HIDE_TRANSIENT);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 691e1c4..1dde5c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -150,14 +150,26 @@
     private KeyguardViewMediator mKeyguardViewMediator;
     private ScrimController mScrimController;
     private StatusBar mStatusBar;
-    private int mPendingAuthenticatedUserId = -1;
-    private BiometricSourceType mPendingAuthenticatedBioSourceType = null;
+    private PendingAuthenticated mPendingAuthenticated = null;
     private boolean mPendingShowBouncer;
     private boolean mHasScreenTurnedOnSinceAuthenticating;
     private boolean mFadedAwayAfterWakeAndUnlock;
 
     private final MetricsLogger mMetricsLogger;
 
+    private static final class PendingAuthenticated {
+        public final int userId;
+        public final BiometricSourceType biometricSourceType;
+        public final boolean isStrongBiometric;
+
+        PendingAuthenticated(int userId, BiometricSourceType biometricSourceType,
+                boolean isStrongBiometric) {
+            this.userId = userId;
+            this.biometricSourceType = biometricSourceType;
+            this.isStrongBiometric = isStrongBiometric;
+        }
+    }
+
     @Inject
     public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
             KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
@@ -251,28 +263,30 @@
     }
 
     @Override
-    public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+    public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+            boolean isStrongBiometric) {
         Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated");
         if (mUpdateMonitor.isGoingToSleep()) {
-            mPendingAuthenticatedUserId = userId;
-            mPendingAuthenticatedBioSourceType = biometricSourceType;
+            mPendingAuthenticated = new PendingAuthenticated(userId, biometricSourceType,
+                    isStrongBiometric);
             Trace.endSection();
             return;
         }
         mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
                 .setType(MetricsEvent.TYPE_SUCCESS).setSubtype(toSubtype(biometricSourceType)));
         boolean unlockAllowed = mKeyguardBypassController.onBiometricAuthenticated(
-                biometricSourceType);
+                biometricSourceType, isStrongBiometric);
         if (unlockAllowed) {
             mKeyguardViewMediator.userActivity();
-            startWakeAndUnlock(biometricSourceType);
+            startWakeAndUnlock(biometricSourceType, isStrongBiometric);
         } else {
             Log.d(TAG, "onBiometricAuthenticated aborted by bypass controller");
         }
     }
 
-    public void startWakeAndUnlock(BiometricSourceType biometricSourceType) {
-        startWakeAndUnlock(calculateMode(biometricSourceType));
+    public void startWakeAndUnlock(BiometricSourceType biometricSourceType,
+            boolean isStrongBiometric) {
+        startWakeAndUnlock(calculateMode(biometricSourceType, isStrongBiometric));
     }
 
     public void startWakeAndUnlock(@WakeAndUnlockMode int mode) {
@@ -373,45 +387,46 @@
     public void onStartedGoingToSleep(int why) {
         resetMode();
         mFadedAwayAfterWakeAndUnlock = false;
-        mPendingAuthenticatedUserId = -1;
-        mPendingAuthenticatedBioSourceType = null;
+        mPendingAuthenticated = null;
     }
 
     @Override
     public void onFinishedGoingToSleep(int why) {
         Trace.beginSection("BiometricUnlockController#onFinishedGoingToSleep");
-        BiometricSourceType pendingType = mPendingAuthenticatedBioSourceType;
-        int pendingUserId = mPendingAuthenticatedUserId;
-        if (pendingUserId != -1 && pendingType != null) {
+        if (mPendingAuthenticated != null) {
             // Post this to make sure it's executed after the device is fully locked.
-            mHandler.post(() -> onBiometricAuthenticated(pendingUserId, pendingType));
+            mHandler.post(() -> onBiometricAuthenticated(mPendingAuthenticated.userId,
+                    mPendingAuthenticated.biometricSourceType,
+                    mPendingAuthenticated.isStrongBiometric));
+            mPendingAuthenticated = null;
         }
-        mPendingAuthenticatedUserId = -1;
-        mPendingAuthenticatedBioSourceType = null;
         Trace.endSection();
     }
 
     public boolean hasPendingAuthentication() {
-        return mPendingAuthenticatedUserId != -1
-                && mUpdateMonitor.isUnlockingWithBiometricAllowed()
-                && mPendingAuthenticatedUserId == KeyguardUpdateMonitor.getCurrentUser();
+        return mPendingAuthenticated != null
+                && mUpdateMonitor
+                    .isUnlockingWithBiometricAllowed(mPendingAuthenticated.isStrongBiometric)
+                && mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser();
     }
 
     public int getMode() {
         return mMode;
     }
 
-    private @WakeAndUnlockMode int calculateMode(BiometricSourceType biometricSourceType) {
+    private @WakeAndUnlockMode int calculateMode(BiometricSourceType biometricSourceType,
+            boolean isStrongBiometric) {
         if (biometricSourceType == BiometricSourceType.FACE
                 || biometricSourceType == BiometricSourceType.IRIS) {
-            return calculateModeForPassiveAuth();
+            return calculateModeForPassiveAuth(isStrongBiometric);
         } else {
-            return calculateModeForFingerprint();
+            return calculateModeForFingerprint(isStrongBiometric);
         }
     }
 
-    private @WakeAndUnlockMode int calculateModeForFingerprint() {
-        boolean unlockingAllowed = mUpdateMonitor.isUnlockingWithBiometricAllowed();
+    private @WakeAndUnlockMode int calculateModeForFingerprint(boolean isStrongBiometric) {
+        boolean unlockingAllowed =
+                mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric);
         boolean deviceDreaming = mUpdateMonitor.isDreaming();
 
         if (!mUpdateMonitor.isDeviceInteractive()) {
@@ -440,8 +455,9 @@
         return MODE_NONE;
     }
 
-    private @WakeAndUnlockMode int calculateModeForPassiveAuth() {
-        boolean unlockingAllowed = mUpdateMonitor.isUnlockingWithBiometricAllowed();
+    private @WakeAndUnlockMode int calculateModeForPassiveAuth(boolean isStrongBiometric) {
+        boolean unlockingAllowed =
+                mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric);
         boolean deviceDreaming = mUpdateMonitor.isDreaming();
         boolean bypass = mKeyguardBypassController.getBypassEnabled();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index b4d0d47..03918e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -38,11 +38,20 @@
     private val mKeyguardStateController: KeyguardStateController
     private val statusBarStateController: StatusBarStateController
     private var hasFaceFeature: Boolean
+    private var pendingUnlock: PendingUnlock? = null
 
     /**
+     * Pending unlock info:
+     *
      * The pending unlock type which is set if the bypass was blocked when it happened.
+     *
+     * Whether the pending unlock type is strong biometric or non-strong biometric
+     * (i.e. weak or convenience).
      */
-    private var pendingUnlockType: BiometricSourceType? = null
+    private data class PendingUnlock(
+        val pendingUnlockType: BiometricSourceType,
+        val isStrongBiometric: Boolean
+    )
 
     lateinit var unlockController: BiometricUnlockController
     var isPulseExpanding = false
@@ -86,7 +95,7 @@
         statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
             override fun onStateChanged(newState: Int) {
                 if (newState != StatusBarState.KEYGUARD) {
-                    pendingUnlockType = null
+                    pendingUnlock = null
                 }
             }
         })
@@ -101,7 +110,7 @@
         lockscreenUserManager.addUserChangedListener(
                 object : NotificationLockscreenUserManager.UserChangedListener {
                     override fun onUserChanged(userId: Int) {
-                        pendingUnlockType = null
+                        pendingUnlock = null
                     }
                 })
     }
@@ -111,11 +120,14 @@
      *
      * @return false if we can not wake and unlock right now
      */
-    fun onBiometricAuthenticated(biometricSourceType: BiometricSourceType): Boolean {
+    fun onBiometricAuthenticated(
+        biometricSourceType: BiometricSourceType,
+        isStrongBiometric: Boolean
+    ): Boolean {
         if (bypassEnabled) {
             val can = canBypass()
             if (!can && (isPulseExpanding || qSExpanded)) {
-                pendingUnlockType = biometricSourceType
+                pendingUnlock = PendingUnlock(biometricSourceType, isStrongBiometric)
             }
             return can
         }
@@ -123,10 +135,12 @@
     }
 
     fun maybePerformPendingUnlock() {
-        if (pendingUnlockType != null) {
-            if (onBiometricAuthenticated(pendingUnlockType!!)) {
-                unlockController.startWakeAndUnlock(pendingUnlockType)
-                pendingUnlockType = null
+        if (pendingUnlock != null) {
+            if (onBiometricAuthenticated(pendingUnlock!!.pendingUnlockType,
+                            pendingUnlock!!.isStrongBiometric)) {
+                unlockController.startWakeAndUnlock(pendingUnlock!!.pendingUnlockType,
+                        pendingUnlock!!.isStrongBiometric)
+                pendingUnlock = null
             }
         }
     }
@@ -162,12 +176,17 @@
     }
 
     fun onStartedGoingToSleep() {
-        pendingUnlockType = null
+        pendingUnlock = null
     }
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         pw.println("KeyguardBypassController:")
-        pw.println("  pendingUnlockType: $pendingUnlockType")
+        if (pendingUnlock != null) {
+            pw.println("  mPendingUnlock.pendingUnlockType: ${pendingUnlock!!.pendingUnlockType}")
+            pw.println("  mPendingUnlock.isStrongBiometric: ${pendingUnlock!!.isStrongBiometric}")
+        } else {
+            pw.println("  mPendingUnlock: $pendingUnlock")
+        }
         pw.println("  bypassEnabled: $bypassEnabled")
         pw.println("  canBypass: ${canBypass()}")
         pw.println("  bouncerShowing: $bouncerShowing")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 60589843..61cef68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -387,7 +387,12 @@
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         boolean fingerprintRunning = mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
-        boolean unlockingAllowed = mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed();
+        // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+        // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
+        // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+        // check of whether non-strong biometric is allowed
+        boolean unlockingAllowed = mKeyguardUpdateMonitor
+                        .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */);
         if (fingerprintRunning && unlockingAllowed) {
             AccessibilityNodeInfo.AccessibilityAction unlock
                     = new AccessibilityNodeInfo.AccessibilityAction(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index fb7976f..c61d7bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -202,8 +202,10 @@
 
                 @Override
                 public void onBiometricAuthenticated(int userId,
-                        BiometricSourceType biometricSourceType) {
-                    if (mFirstBypassAttempt && mUpdateMonitor.isUnlockingWithBiometricAllowed()) {
+                        BiometricSourceType biometricSourceType,
+                        boolean isStrongBiometric) {
+                    if (mFirstBypassAttempt
+                            && mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric)) {
                         mDelayShowingKeyguardStatusBar = true;
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 0ab08a8..a7f60d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -293,13 +293,12 @@
         }
 
         @Override
-        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+                boolean isStrongBiometric) {
             Trace.beginSection("KeyguardUpdateMonitorCallback#onBiometricAuthenticated");
-            if (!mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()) {
-                Trace.endSection();
-                return;
+            if (mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric)) {
+                update(false /* updateAlways */);
             }
-            update(false /* updateAlways */);
             Trace.endSection();
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index befe3e1..6a093963 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -22,6 +22,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -75,6 +76,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 @SmallTest
@@ -117,6 +119,8 @@
     private SubscriptionManager mSubscriptionManager;
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private Executor mBackgroundExecutor;
     private TestableLooper mTestableLooper;
     private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
@@ -137,7 +141,9 @@
         when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
         when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
         when(mUserManager.isPrimaryUser()).thenReturn(true);
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mStrongAuthTracker
+                .isUnlockingWithBiometricAllowed(anyBoolean() /* isStrongBiometric */))
+                .thenReturn(true);
         context.addMockSystemService(TrustManager.class, mTrustManager);
         context.addMockSystemService(FingerprintManager.class, mFingerprintManager);
         context.addMockSystemService(BiometricManager.class, mBiometricManager);
@@ -450,7 +456,10 @@
 
     @Test
     public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() {
-        mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser());
+        // test whether face will be skipped if authenticated, so the value of isStrongBiometric
+        // doesn't matter here
+        mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
+                true /* isStrongBiometric */);
         mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true);
         mTestableLooper.processAllMessages();
 
@@ -460,18 +469,36 @@
     @Test
     public void testGetUserCanSkipBouncer_whenFace() {
         int user = KeyguardUpdateMonitor.getCurrentUser();
-        mKeyguardUpdateMonitor.onFaceAuthenticated(user);
+        mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isStrongBiometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
 
     @Test
+    public void testGetUserCanSkipBouncer_whenFace_nonStrongAndDisallowed() {
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+                .thenReturn(false);
+        int user = KeyguardUpdateMonitor.getCurrentUser();
+        mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isStrongBiometric */);
+        assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
+    }
+
+    @Test
     public void testGetUserCanSkipBouncer_whenFingerprint() {
         int user = KeyguardUpdateMonitor.getCurrentUser();
-        mKeyguardUpdateMonitor.onFingerprintAuthenticated(user);
+        mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isStrongBiometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
 
     @Test
+    public void testGetUserCanSkipBouncer_whenFingerprint_nonStrongAndDisallowed() {
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+                .thenReturn(false);
+        int user = KeyguardUpdateMonitor.getCurrentUser();
+        mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isStrongBiometric */);
+        assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
+    }
+
+    @Test
     public void testGetUserCanSkipBouncer_whenTrust() {
         int user = KeyguardUpdateMonitor.getCurrentUser();
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, user, 0 /* flags */);
@@ -585,7 +612,7 @@
         protected TestableKeyguardUpdateMonitor(Context context) {
             super(context,
                     TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
-                    mBroadcastDispatcher, mDumpController);
+                    mBroadcastDispatcher, mDumpController, mBackgroundExecutor);
             mStrongAuthTracker = KeyguardUpdateMonitorTest.this.mStrongAuthTracker;
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 1d4b4be..581d795 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -124,7 +125,7 @@
         mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
         mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
 
-        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mKeyguardUpdateMonitor.isScreenOn()).thenReturn(true);
         when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
         when(mIndicationArea.findViewById(R.id.keyguard_indication_text)).thenReturn(mTextView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 769b774..813923d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -97,7 +97,8 @@
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
         when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
-        when(mKeyguardBypassController.onBiometricAuthenticated(any())).thenReturn(true);
+        when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
+                .thenReturn(true);
         when(mKeyguardBypassController.canPlaySubtleWindowAnimations()).thenReturn(true);
         mContext.addMockSystemService(PowerManager.class, mPowerManager);
         mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
@@ -112,11 +113,28 @@
 
     @Test
     public void onBiometricAuthenticated_whenFingerprintAndBiometricsDisallowed_showBouncer() {
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */))
+                .thenReturn(false);
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FINGERPRINT);
+                BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
         verify(mStatusBarKeyguardViewManager).showBouncer(eq(false));
         verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
                 anyFloat());
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
+    }
+
+    @Test
+    public void onBiometricAuthenticated_whenFingerprint_nonStrongBioDisallowed_showBouncer() {
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+                .thenReturn(false);
+        mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
+                BiometricSourceType.FINGERPRINT, false /* isStrongBiometric */);
+        verify(mStatusBarKeyguardViewManager).showBouncer(eq(false));
+        verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
+                anyFloat());
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
     }
 
     @Test
@@ -124,44 +142,60 @@
         reset(mUpdateMonitor);
         reset(mStatusBarKeyguardViewManager);
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mDozeScrimController.isPulsing()).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FINGERPRINT);
+                BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
 
         verify(mKeyguardViewMediator).onWakeAndUnlocking();
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
     }
 
     @Test
     public void onBiometricAuthenticated_whenFingerprint_dismissKeyguard() {
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FINGERPRINT);
+                BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
 
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
         verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
                 anyFloat());
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
     }
 
     @Test
     public void onBiometricAuthenticated_whenFingerprintOnBouncer_dismissBouncer() {
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FINGERPRINT);
+                BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
 
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_DISMISS_BOUNCER);
     }
 
     @Test
     public void onBiometricAuthenticated_whenFace_dontDismissKeyguard() {
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
                 anyBoolean(), anyFloat());
         verify(mStatusBarKeyguardViewManager, never()).notifyKeyguardAuthenticated(anyBoolean());
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_NONE);
     }
 
     @Test
@@ -169,13 +203,17 @@
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
 
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
                 anyBoolean(), anyFloat());
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_UNLOCK_FADING);
     }
 
     @Test
@@ -184,9 +222,11 @@
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
 
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(false);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         // Wake up before showing the bouncer
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(eq(false));
@@ -202,9 +242,11 @@
         reset(mUpdateMonitor);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
 
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(false);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
         verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
@@ -215,23 +257,30 @@
 
     @Test
     public void onBiometricAuthenticated_whenFaceOnBouncer_dismissBouncer() {
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_DISMISS_BOUNCER);
     }
 
     @Test
     public void onBiometricAuthenticated_whenBypassOnBouncer_dismissBouncer() {
         reset(mKeyguardBypassController);
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
-        when(mKeyguardBypassController.onBiometricAuthenticated(any())).thenReturn(true);
+        when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
+                .thenReturn(true);
         when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
         assertThat(mBiometricUnlockController.getMode())
@@ -240,11 +289,13 @@
 
     @Test
     public void onBiometricAuthenticated_whenBypassOnBouncer_respectsCanPlaySubtleAnim() {
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
         when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
         assertThat(mBiometricUnlockController.getMode())
@@ -255,13 +306,17 @@
     public void onBiometricAuthenticated_whenFaceAndPulsing_dontDismissKeyguard() {
         reset(mUpdateMonitor);
         reset(mStatusBarKeyguardViewManager);
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mDozeScrimController.isPulsing()).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
                 anyBoolean(), anyFloat());
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_ONLY_WAKE);
     }
 
     @Test
@@ -270,8 +325,10 @@
         mBiometricUnlockController.onFinishedGoingToSleep(-1);
         verify(mHandler, never()).post(any());
 
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(1 /* userId */,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
         mBiometricUnlockController.onFinishedGoingToSleep(-1);
         verify(mHandler).post(any());
     }
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 204f072..8ed221d 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -311,6 +311,7 @@
                 }
 
                 authenticator = new FingerprintAuthenticator(fingerprintService);
+                fingerprintService.initConfiguredStrength(config.mStrength);
                 break;
 
             case TYPE_FACE:
@@ -322,6 +323,7 @@
                 }
 
                 authenticator = new FaceAuthenticator(faceService);
+                faceService.initConfiguredStrength(config.mStrength);
                 break;
 
             case TYPE_IRIS:
@@ -333,6 +335,7 @@
                 }
 
                 authenticator = new IrisAuthenticator(irisService);
+                irisService.initConfiguredStrength(config.mStrength);
                 break;
 
             default:
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 0e70994..74c70df 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -31,6 +31,7 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricService;
@@ -106,6 +107,7 @@
     private PerformanceStats mPerformanceStats;
     protected int mCurrentUserId = UserHandle.USER_NULL;
     protected long mHalDeviceId;
+    private int mOEMStrength; // Tracks the OEM configured biometric modality strength
     // Tracks if the current authentication makes use of CryptoObjects.
     protected boolean mIsCrypto;
     // Normal authentications are tracked by mPerformanceMap.
@@ -681,6 +683,20 @@
                 statsModality(), BiometricsProtoEnums.ISSUE_HAL_DEATH);
     }
 
+    protected void initConfiguredStrengthInternal(int strength) {
+        if (DEBUG) {
+            Slog.d(getTag(), "initConfiguredStrengthInternal(" + strength + ")");
+        }
+        mOEMStrength = strength;
+    }
+
+    protected boolean isStrongBiometric() {
+        // TODO(b/141025588): need to calculate actual strength when downgrading tiers
+        final int biometricBits = mOEMStrength
+                & BiometricManager.Authenticators.BIOMETRIC_MIN_STRENGTH;
+        return biometricBits == BiometricManager.Authenticators.BIOMETRIC_STRONG;
+    }
+
     protected ClientMonitor getCurrentClient() {
         return mCurrentClient;
     }
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 31c3d4d..a87a455 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -740,6 +740,12 @@
             }
             return 0;
         }
+
+        @Override // Binder call
+        public void initConfiguredStrength(int strength) {
+            checkPermission(USE_BIOMETRIC_INTERNAL);
+            initConfiguredStrengthInternal(strength);
+        }
     }
 
     /**
@@ -809,7 +815,7 @@
             if (mFaceServiceReceiver != null) {
                 if (biometric == null || biometric instanceof Face) {
                     mFaceServiceReceiver.onAuthenticationSucceeded(deviceId, (Face) biometric,
-                            userId);
+                            userId, isStrongBiometric());
                 } else {
                     Slog.e(TAG, "onAuthenticationSucceeded received non-face biometric");
                 }
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 0a61988..83aa9cf 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -21,6 +21,7 @@
 import static android.Manifest.permission.MANAGE_FINGERPRINT;
 import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
 import static android.Manifest.permission.USE_BIOMETRIC;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.Manifest.permission.USE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 
@@ -462,6 +463,12 @@
             checkPermission(MANAGE_FINGERPRINT);
             mClientActiveCallbacks.remove(callback);
         }
+
+        @Override // Binder call
+        public void initConfiguredStrength(int strength) {
+            checkPermission(USE_BIOMETRIC_INTERNAL);
+            initConfiguredStrengthInternal(strength);
+        }
     }
 
     /**
@@ -526,8 +533,8 @@
                 throws RemoteException {
             if (mFingerprintServiceReceiver != null) {
                 if (biometric == null || biometric instanceof Fingerprint) {
-                    mFingerprintServiceReceiver
-                            .onAuthenticationSucceeded(deviceId, (Fingerprint) biometric, userId);
+                    mFingerprintServiceReceiver.onAuthenticationSucceeded(deviceId,
+                            (Fingerprint) biometric, userId, isStrongBiometric());
                 } else {
                     Slog.e(TAG, "onAuthenticationSucceeded received non-fingerprint biometric");
                 }
diff --git a/services/core/java/com/android/server/biometrics/iris/IrisService.java b/services/core/java/com/android/server/biometrics/iris/IrisService.java
index 2817315..903ae6b 100644
--- a/services/core/java/com/android/server/biometrics/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/iris/IrisService.java
@@ -16,9 +16,12 @@
 
 package com.android.server.biometrics.iris;
 
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.iris.IIrisService;
 
 import com.android.server.biometrics.AuthenticationClient;
 import com.android.server.biometrics.BiometricServiceBase;
@@ -42,6 +45,17 @@
     private static final String TAG = "IrisService";
 
     /**
+     * Receives the incoming binder calls from IrisManager.
+     */
+    private final class IrisServiceWrapper extends IIrisService.Stub {
+        @Override // Binder call
+        public void initConfiguredStrength(int strength) {
+            checkPermission(USE_BIOMETRIC_INTERNAL);
+            initConfiguredStrengthInternal(strength);
+        }
+    }
+
+    /**
      * Initializes the system service.
      * <p>
      * Subclasses must define a single argument constructor that accepts the context
@@ -57,6 +71,7 @@
     @Override
     public void onStart() {
         super.onStart();
+        publishBinderService(Context.IRIS_SERVICE, new IrisServiceWrapper());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 1f4048f..15dfab9 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -17,6 +17,7 @@
 package com.android.server.locksettings;
 
 import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
+import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.READ_CONTACTS;
 import static android.content.Context.KEYGUARD_SERVICE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -178,6 +179,7 @@
 public class LockSettingsService extends ILockSettings.Stub {
     private static final String TAG = "LockSettingsService";
     private static final String PERMISSION = ACCESS_KEYGUARD_SECURE_STORAGE;
+    private static final String BIOMETRIC_PERMISSION = MANAGE_BIOMETRIC;
     private static final boolean DEBUG = false;
 
     private static final int PROFILE_KEY_IV_SIZE = 12;
@@ -1050,6 +1052,10 @@
         }
     }
 
+    private final void checkBiometricPermission() {
+        mContext.enforceCallingOrSelfPermission(BIOMETRIC_PERMISSION, "LockSettingsBiometric");
+    }
+
     @Override
     public boolean hasSecureLockScreen() {
         return mHasSecureLockScreen;
@@ -2304,6 +2310,18 @@
     }
 
     @Override
+    public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
+        checkBiometricPermission();
+        mStrongAuth.reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+    }
+
+    @Override
+    public void scheduleNonStrongBiometricIdleTimeout(int userId) {
+        checkBiometricPermission();
+        mStrongAuth.scheduleNonStrongBiometricIdleTimeout(userId);
+    }
+
+    @Override
     public void userPresent(int userId) {
         checkWritePermission(userId);
         mStrongAuth.reportUnlock(userId);
@@ -3191,6 +3209,12 @@
         mStorage.dump(pw);
         pw.println();
         pw.decreaseIndent();
+
+        pw.println("StrongAuth:");
+        pw.increaseIndent();
+        mStrongAuth.dump(pw);
+        pw.println();
+        pw.decreaseIndent();
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
index 91cf53e..fbee6f4 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -17,6 +17,7 @@
 package com.android.server.locksettings;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 
 import android.app.AlarmManager;
@@ -32,8 +33,10 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
 
 /**
@@ -42,6 +45,7 @@
 public class LockSettingsStrongAuth {
 
     private static final String TAG = "LockSettings";
+    private static final boolean DEBUG = false;
 
     private static final int MSG_REQUIRE_STRONG_AUTH = 1;
     private static final int MSG_REGISTER_TRACKER = 2;
@@ -49,15 +53,40 @@
     private static final int MSG_REMOVE_USER = 4;
     private static final int MSG_SCHEDULE_STRONG_AUTH_TIMEOUT = 5;
     private static final int MSG_NO_LONGER_REQUIRE_STRONG_AUTH = 6;
+    private static final int MSG_SCHEDULE_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
+    private static final int MSG_STRONG_BIOMETRIC_UNLOCK = 8;
+    private static final int MSG_SCHEDULE_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT = 9;
 
     private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
             "LockSettingsStrongAuth.timeoutForUser";
+    private static final String NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG =
+            "LockSettingsPrimaryAuth.nonStrongBiometricTimeoutForUser";
+    private static final String NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG =
+            "LockSettingsPrimaryAuth.nonStrongBiometricIdleTimeoutForUser";
+
+    /**
+     * Default and maximum timeout in milliseconds after which unlocking with weak auth times out,
+     * i.e. the user has to use a strong authentication method like password, PIN or pattern.
+     */
+    public static final long DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 24h
+    public static final long DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS =
+            4 * 60 * 60 * 1000; // 4h
 
     private final RemoteCallbackList<IStrongAuthTracker> mTrackers = new RemoteCallbackList<>();
     private final SparseIntArray mStrongAuthForUser = new SparseIntArray();
+    private final SparseBooleanArray mIsNonStrongBiometricAllowedForUser = new SparseBooleanArray();
     private final ArrayMap<Integer, StrongAuthTimeoutAlarmListener>
             mStrongAuthTimeoutAlarmListenerForUser = new ArrayMap<>();
+    // Track non-strong biometric timeout
+    private final ArrayMap<Integer, NonStrongBiometricTimeoutAlarmListener>
+            mNonStrongBiometricTimeoutAlarmListener = new ArrayMap<>();
+    // Track non-strong biometric idle timeout
+    private final ArrayMap<Integer, NonStrongBiometricIdleTimeoutAlarmListener>
+            mNonStrongBiometricIdleTimeoutAlarmListener = new ArrayMap<>();
+
     private final int mDefaultStrongAuthFlags;
+    private final boolean mDefaultIsNonStrongBiometricAllowed = true;
+
     private final Context mContext;
 
     private AlarmManager mAlarmManager;
@@ -80,6 +109,17 @@
                 Slog.e(TAG, "Exception while adding StrongAuthTracker.", e);
             }
         }
+
+        for (int i = 0; i < mIsNonStrongBiometricAllowedForUser.size(); i++) {
+            int key = mIsNonStrongBiometricAllowedForUser.keyAt(i);
+            boolean value = mIsNonStrongBiometricAllowedForUser.valueAt(i);
+            try {
+                tracker.onIsNonStrongBiometricAllowedChanged(value, key);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Exception while adding StrongAuthTracker: "
+                        + "IsNonStrongBiometricAllowedChanged.", e);
+            }
+        }
     }
 
     private void handleRemoveStrongAuthTracker(IStrongAuthTracker tracker) {
@@ -134,6 +174,13 @@
             mStrongAuthForUser.removeAt(index);
             notifyStrongAuthTrackers(mDefaultStrongAuthFlags, userId);
         }
+
+        index = mIsNonStrongBiometricAllowedForUser.indexOfKey(userId);
+        if (index >= 0) {
+            mIsNonStrongBiometricAllowedForUser.removeAt(index);
+            notifyStrongAuthTrackersForIsNonStrongBiometricAllowed(
+                    mDefaultIsNonStrongBiometricAllowed, userId);
+        }
     }
 
     private void handleScheduleStrongAuthTimeout(int userId) {
@@ -151,6 +198,125 @@
         // schedule a new alarm listener for the user
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when, STRONG_AUTH_TIMEOUT_ALARM_TAG,
                 alarm, mHandler);
+
+        // cancel current non-strong biometric alarm listener for the user (if there was one)
+        cancelNonStrongBiometricAlarmListener(userId);
+        // cancel current non-strong biometric idle alarm listener for the user (if there was one)
+        cancelNonStrongBiometricIdleAlarmListener(userId);
+        // re-allow unlock with non-strong biometrics
+        setIsNonStrongBiometricAllowed(true, userId);
+    }
+
+    private void handleScheduleNonStrongBiometricTimeout(int userId) {
+        if (DEBUG) Slog.d(TAG, "handleScheduleNonStrongBiometricTimeout for userId=" + userId);
+        long when = SystemClock.elapsedRealtime() + DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS;
+        NonStrongBiometricTimeoutAlarmListener alarm = mNonStrongBiometricTimeoutAlarmListener
+                .get(userId);
+        if (alarm != null) {
+            // Unlock with non-strong biometric will not affect the existing non-strong biometric
+            // timeout alarm
+            if (DEBUG) {
+                Slog.d(TAG, "There is an existing alarm for non-strong biometric"
+                        + " fallback timeout, so do not re-schedule");
+            }
+        } else {
+            if (DEBUG) {
+                Slog.d(TAG, "Schedule a new alarm for non-strong biometric fallback timeout");
+            }
+            alarm = new NonStrongBiometricTimeoutAlarmListener(userId);
+            mNonStrongBiometricTimeoutAlarmListener.put(userId, alarm);
+            // schedule a new alarm listener for the user
+            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when,
+                    NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG, alarm, mHandler);
+        }
+
+        // cancel current non-strong biometric idle alarm listener for the user (if there was one)
+        cancelNonStrongBiometricIdleAlarmListener(userId);
+    }
+
+    private void handleStrongBiometricUnlock(int userId) {
+        if (DEBUG) Slog.d(TAG, "handleStrongBiometricUnlock for userId=" + userId);
+        // cancel current non-strong biometric alarm listener for the user (if there was one)
+        cancelNonStrongBiometricAlarmListener(userId);
+        // cancel current non-strong biometric idle alarm listener for the user (if there was one)
+        cancelNonStrongBiometricIdleAlarmListener(userId);
+        // re-allow unlock with non-strong biometrics
+        setIsNonStrongBiometricAllowed(true, userId);
+    }
+
+    private void cancelNonStrongBiometricAlarmListener(int userId) {
+        if (DEBUG) Slog.d(TAG, "cancelNonStrongBiometricAlarmListener for userId=" + userId);
+        NonStrongBiometricTimeoutAlarmListener alarm = mNonStrongBiometricTimeoutAlarmListener
+                .get(userId);
+        if (alarm != null) {
+            if (DEBUG) Slog.d(TAG, "Cancel alarm for non-strong biometric fallback timeout");
+            mAlarmManager.cancel(alarm);
+            // need to remove the alarm when cancelled by primary auth or strong biometric
+            mNonStrongBiometricTimeoutAlarmListener.remove(userId);
+        }
+    }
+
+    private void cancelNonStrongBiometricIdleAlarmListener(int userId) {
+        if (DEBUG) Slog.d(TAG, "cancelNonStrongBiometricIdleAlarmListener for userId=" + userId);
+        // cancel idle alarm listener by any unlocks (i.e. primary auth, strong biometric,
+        // non-strong biometric)
+        NonStrongBiometricIdleTimeoutAlarmListener alarm =
+                mNonStrongBiometricIdleTimeoutAlarmListener.get(userId);
+        if (alarm != null) {
+            if (DEBUG) Slog.d(TAG, "Cancel alarm for non-strong biometric idle timeout");
+            mAlarmManager.cancel(alarm);
+        }
+    }
+
+    private void setIsNonStrongBiometricAllowed(boolean allowed, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "setIsNonStrongBiometricAllowed for allowed=" + allowed
+                    + ", userId=" + userId);
+        }
+        if (userId == UserHandle.USER_ALL) {
+            for (int i = 0; i < mIsNonStrongBiometricAllowedForUser.size(); i++) {
+                int key = mIsNonStrongBiometricAllowedForUser.keyAt(i);
+                setIsNonStrongBiometricAllowedOneUser(allowed, key);
+            }
+        } else {
+            setIsNonStrongBiometricAllowedOneUser(allowed, userId);
+        }
+    }
+
+    private void setIsNonStrongBiometricAllowedOneUser(boolean allowed, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "setIsNonStrongBiometricAllowedOneUser for allowed=" + allowed
+                    + ", userId=" + userId);
+        }
+        boolean oldValue = mIsNonStrongBiometricAllowedForUser.get(userId,
+                mDefaultIsNonStrongBiometricAllowed);
+        if (allowed != oldValue) {
+            if (DEBUG) {
+                Slog.d(TAG, "mIsNonStrongBiometricAllowedForUser value changed:"
+                        + " oldValue=" + oldValue + ", allowed=" + allowed);
+            }
+            mIsNonStrongBiometricAllowedForUser.put(userId, allowed);
+            notifyStrongAuthTrackersForIsNonStrongBiometricAllowed(allowed, userId);
+        }
+    }
+
+    private void handleScheduleNonStrongBiometricIdleTimeout(int userId) {
+        if (DEBUG) Slog.d(TAG, "handleScheduleNonStrongBiometricIdleTimeout for userId=" + userId);
+        long when = SystemClock.elapsedRealtime() + DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS;
+        // cancel current alarm listener for the user (if there was one)
+        NonStrongBiometricIdleTimeoutAlarmListener alarm =
+                mNonStrongBiometricIdleTimeoutAlarmListener.get(userId);
+        if (alarm != null) {
+            if (DEBUG) Slog.d(TAG, "Cancel existing alarm for non-strong biometric idle timeout");
+            mAlarmManager.cancel(alarm);
+        } else {
+            alarm = new NonStrongBiometricIdleTimeoutAlarmListener(userId);
+            mNonStrongBiometricIdleTimeoutAlarmListener.put(userId, alarm);
+        }
+        // schedule a new alarm listener for the user
+        if (DEBUG) Slog.d(TAG, "Schedule a new alarm for non-strong biometric idle timeout");
+        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when,
+                NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG, alarm, mHandler);
     }
 
     private void notifyStrongAuthTrackers(int strongAuthReason, int userId) {
@@ -170,6 +336,29 @@
         }
     }
 
+    private void notifyStrongAuthTrackersForIsNonStrongBiometricAllowed(boolean allowed,
+            int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyStrongAuthTrackersForIsNonStrongBiometricAllowed"
+                    + " for allowed=" + allowed + ", userId=" + userId);
+        }
+        int i = mTrackers.beginBroadcast();
+        try {
+            while (i > 0) {
+                i--;
+                try {
+                    mTrackers.getBroadcastItem(i).onIsNonStrongBiometricAllowedChanged(
+                            allowed, userId);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception while notifying StrongAuthTracker: "
+                            + "IsNonStrongBiometricAllowedChanged.", e);
+                }
+            }
+        } finally {
+            mTrackers.finishBroadcast();
+        }
+    }
+
     public void registerStrongAuthTracker(IStrongAuthTracker tracker) {
         mHandler.obtainMessage(MSG_REGISTER_TRACKER, tracker).sendToTarget();
     }
@@ -207,11 +396,45 @@
         requireStrongAuth(STRONG_AUTH_NOT_REQUIRED, userId);
     }
 
+    /**
+     * Report successful unlocking with primary auth
+     */
     public void reportSuccessfulStrongAuthUnlock(int userId) {
         final int argNotUsed = 0;
         mHandler.obtainMessage(MSG_SCHEDULE_STRONG_AUTH_TIMEOUT, userId, argNotUsed).sendToTarget();
     }
 
+    /**
+     * Report successful unlocking with biometric
+     */
+    public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "reportSuccessfulBiometricUnlock for isStrongBiometric="
+                    + isStrongBiometric + ", userId=" + userId);
+        }
+        final int argNotUsed = 0;
+        if (isStrongBiometric) { // unlock with strong biometric
+            mHandler.obtainMessage(MSG_STRONG_BIOMETRIC_UNLOCK, userId, argNotUsed)
+                    .sendToTarget();
+        } else { // unlock with non-strong biometric (i.e. weak or convenience)
+            mHandler.obtainMessage(MSG_SCHEDULE_NON_STRONG_BIOMETRIC_TIMEOUT, userId, argNotUsed)
+                    .sendToTarget();
+        }
+    }
+
+    /**
+     * Schedule idle timeout for non-strong biometric (i.e. weak or convenience)
+     */
+    public void scheduleNonStrongBiometricIdleTimeout(int userId) {
+        if (DEBUG) Slog.d(TAG, "scheduleNonStrongBiometricIdleTimeout for userId=" + userId);
+        final int argNotUsed = 0;
+        mHandler.obtainMessage(MSG_SCHEDULE_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT, userId, argNotUsed)
+                .sendToTarget();
+    }
+
+    /**
+     * Alarm of fallback timeout for primary auth
+     */
     private class StrongAuthTimeoutAlarmListener implements OnAlarmListener {
 
         private final int mUserId;
@@ -226,6 +449,41 @@
         }
     }
 
+    /**
+     * Alarm of fallback timeout for non-strong biometric (i.e. weak or convenience)
+     */
+    private class NonStrongBiometricTimeoutAlarmListener implements OnAlarmListener {
+
+        private final int mUserId;
+
+        NonStrongBiometricTimeoutAlarmListener(int userId) {
+            mUserId = userId;
+        }
+
+        @Override
+        public void onAlarm() {
+            requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT, mUserId);
+        }
+    }
+
+    /**
+     * Alarm of idle timeout for non-strong biometric (i.e. weak or convenience biometric)
+     */
+    private class NonStrongBiometricIdleTimeoutAlarmListener implements OnAlarmListener {
+
+        private final int mUserId;
+
+        NonStrongBiometricIdleTimeoutAlarmListener(int userId) {
+            mUserId = userId;
+        }
+
+        @Override
+        public void onAlarm() {
+            // disallow unlock with non-strong biometrics
+            setIsNonStrongBiometricAllowed(false, mUserId);
+        }
+    }
+
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -248,8 +506,38 @@
                 case MSG_NO_LONGER_REQUIRE_STRONG_AUTH:
                     handleNoLongerRequireStrongAuth(msg.arg1, msg.arg2);
                     break;
+                case MSG_SCHEDULE_NON_STRONG_BIOMETRIC_TIMEOUT:
+                    handleScheduleNonStrongBiometricTimeout(msg.arg1);
+                    break;
+                case MSG_STRONG_BIOMETRIC_UNLOCK:
+                    handleStrongBiometricUnlock(msg.arg1);
+                    break;
+                case MSG_SCHEDULE_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT:
+                    handleScheduleNonStrongBiometricIdleTimeout(msg.arg1);
+                    break;
             }
         }
     };
 
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("PrimaryAuthFlags state:");
+        pw.increaseIndent();
+        for (int i = 0; i < mStrongAuthForUser.size(); i++) {
+            final int key = mStrongAuthForUser.keyAt(i);
+            final int value = mStrongAuthForUser.valueAt(i);
+            pw.println("userId=" + key + ", primaryAuthFlags=" + Integer.toHexString(value));
+        }
+        pw.println();
+        pw.decreaseIndent();
+
+        pw.println("NonStrongBiometricAllowed state:");
+        pw.increaseIndent();
+        for (int i = 0; i < mIsNonStrongBiometricAllowedForUser.size(); i++) {
+            final int key = mIsNonStrongBiometricAllowedForUser.keyAt(i);
+            final boolean value = mIsNonStrongBiometricAllowedForUser.valueAt(i);
+            pw.println("userId=" + key + ", allowed=" + value);
+        }
+        pw.println();
+        pw.decreaseIndent();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 1c8b00f..30bb38a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -111,6 +111,29 @@
                 any());
     }
 
+    @Test
+    public void testRegisterAuthenticator_callsInitConfiguredStrength() throws Exception {
+
+        final String[] config = {
+                "0:2:15", // ID0:Fingerprint:Strong
+                "1:4:255", // ID1:Iris:Weak
+                "2:8:4095", // ID2:Face:Convenience
+        };
+
+        when(mInjector.getConfiguration(any())).thenReturn(config);
+
+        mAuthService = new AuthService(mContext, mInjector);
+        mAuthService.onStart();
+
+        final int fingerprintStrength = 15;
+        final int irisStrength = 255;
+        final int faceStrength = 4095;
+
+        verify(mFingerprintService).initConfiguredStrength(eq(fingerprintStrength));
+        verify(mIrisService).initConfiguredStrength(eq(irisStrength));
+        verify(mFaceService).initConfiguredStrength(eq(faceStrength));
+    }
+
 
     // TODO(b/141025588): Check that an exception is thrown when the userId != callingUserId
     @Test