Merge "linkToDeath on BiometricPrompt#authenticate" into rvc-dev
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
index 5d334c2..edc8f15 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
@@ -84,11 +84,8 @@
 
     @Override
     public void binderDied() {
-        super.binderDied();
-        // When the binder dies, we should stop the client. This probably belongs in
-        // ClientMonitor's binderDied(), but testing all the cases would be tricky.
-        // AuthenticationClient is the most user-visible case.
-        stop(false /* initiatedByClient */);
+        final boolean clearListener = !isBiometricPrompt();
+        binderDiedInternal(clearListener);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 7e28e94..4ddfe1b 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -113,6 +113,7 @@
     private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11;
     private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12;
     private static final int MSG_ON_SYSTEM_EVENT = 13;
+    private static final int MSG_CLIENT_DIED = 14;
 
     /**
      * Authentication either just called and we have not transitioned to the CALLED state, or
@@ -151,8 +152,13 @@
      * Device credential in AuthController is showing
      */
     static final int STATE_SHOWING_DEVICE_CREDENTIAL = 8;
+    /**
+     * The client binder died, and sensors were authenticating at the time. Cancel has been
+     * requested and we're waiting for the HAL(s) to send ERROR_CANCELED.
+     */
+    static final int STATE_CLIENT_DIED_CANCELLING = 9;
 
-    final class AuthSession {
+    final class AuthSession implements IBinder.DeathRecipient {
         // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from
         // <Biometric>Services before we can start authenticating. Pairs that have been returned
         // are moved to mModalitiesMatched.
@@ -211,7 +217,14 @@
             mCallingUserId = callingUserId;
             mModality = modality;
             mRequireConfirmation = requireConfirmation;
+
             Slog.d(TAG, "New AuthSession, mSysUiSessionId: " + mSysUiSessionId);
+
+            try {
+                mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Unable to link to death");
+            }
         }
 
         boolean isCrypto() {
@@ -231,6 +244,12 @@
         boolean isAllowDeviceCredential() {
             return Utils.isCredentialRequested(mBundle);
         }
+
+        @Override
+        public void binderDied() {
+            Slog.e(TAG, "Binder died, sysUiSessionId: " + mSysUiSessionId);
+            mHandler.obtainMessage(MSG_CLIENT_DIED).sendToTarget();
+        }
     }
 
     private final Injector mInjector;
@@ -370,6 +389,11 @@
                     break;
                 }
 
+                case MSG_CLIENT_DIED: {
+                    handleClientDied();
+                    break;
+                }
+
                 default:
                     Slog.e(TAG, "Unknown message: " + msg);
                     break;
@@ -1391,6 +1415,7 @@
     }
 
     private void handleOnError(int cookie, int modality, int error, int vendorCode) {
+
         Slog.d(TAG, "handleOnError: " + error + " cookie: " + cookie);
         // Errors can either be from the current auth session or the pending auth session.
         // The pending auth session may receive errors such as ERROR_LOCKOUT before
@@ -1431,6 +1456,9 @@
                 } else if (mCurrentAuthSession.mState == STATE_SHOWING_DEVICE_CREDENTIAL) {
                     Slog.d(TAG, "Biometric canceled, ignoring from state: "
                             + mCurrentAuthSession.mState);
+                } else if (mCurrentAuthSession.mState == STATE_CLIENT_DIED_CANCELLING) {
+                    mStatusBarService.hideAuthenticationDialog();
+                    mCurrentAuthSession = null;
                 } else {
                     Slog.e(TAG, "Impossible session error state: "
                             + mCurrentAuthSession.mState);
@@ -1622,6 +1650,36 @@
         }
     }
 
+    private void handleClientDied() {
+        if (mCurrentAuthSession == null) {
+            Slog.e(TAG, "Auth session null");
+            return;
+        }
+
+        Slog.e(TAG, "SysUiSessionId: " + mCurrentAuthSession.mSysUiSessionId
+                + " State: " + mCurrentAuthSession.mState);
+
+        try {
+            // Check if any sensors are authenticating. If so, need to cancel them. When
+            // ERROR_CANCELED is received from the HAL, we hide the dialog and cleanup the session.
+            if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
+                mCurrentAuthSession.mState = STATE_CLIENT_DIED_CANCELLING;
+                cancelInternal(mCurrentAuthSession.mToken,
+                        mCurrentAuthSession.mOpPackageName,
+                        mCurrentAuthSession.mCallingUid,
+                        mCurrentAuthSession.mCallingPid,
+                        mCurrentAuthSession.mCallingUserId,
+                        false /* fromClient */);
+            } else {
+                // If the sensors are not authenticating, set the auth session to null.
+                mStatusBarService.hideAuthenticationDialog();
+                mCurrentAuthSession = null;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception: " + e);
+        }
+    }
+
     /**
      * Invoked when each service has notified that its client is ready to be started. When
      * all biometrics are ready, this invokes the SystemUI dialog through StatusBar.
@@ -1822,11 +1880,11 @@
 
     void cancelInternal(IBinder token, String opPackageName, int callingUid, int callingPid,
             int callingUserId, boolean fromClient) {
-
         if (mCurrentAuthSession == null) {
             Slog.w(TAG, "Skipping cancelInternal");
             return;
-        } else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
+        } else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED
+                && mCurrentAuthSession.mState != STATE_CLIENT_DIED_CANCELLING) {
             Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState);
             return;
         }
diff --git a/services/core/java/com/android/server/biometrics/ClientMonitor.java b/services/core/java/com/android/server/biometrics/ClientMonitor.java
index 942e050..b029695 100644
--- a/services/core/java/com/android/server/biometrics/ClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/ClientMonitor.java
@@ -233,11 +233,17 @@
 
     @Override
     public void binderDied() {
+        binderDiedInternal(true /* clearListener */);
+    }
+
+    void binderDiedInternal(boolean clearListener) {
         // If the current client dies we should cancel the current operation.
         Slog.e(getLogTag(), "Binder died, cancelling client");
         stop(false /* initiatedByClient */);
         mToken = null;
-        mListener = null;
+        if (clearListener) {
+            mListener = null;
+        }
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 285caf3..48ec529 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -115,6 +115,8 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        resetReceivers();
+
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
@@ -147,6 +149,74 @@
     }
 
     @Test
+    public void testClientBinderDied_whenPaused() throws Exception {
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+
+        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+                true /* requireConfirmation */, null /* authenticators */);
+        waitForIdle();
+        verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession),
+                anyInt());
+
+        mBiometricService.mInternalReceiver.onError(
+                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                BiometricAuthenticator.TYPE_FACE,
+                BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+                0 /* vendorCode */);
+        waitForIdle();
+
+        assertEquals(BiometricService.STATE_AUTH_PAUSED,
+                mBiometricService.mCurrentAuthSession.mState);
+
+        mBiometricService.mCurrentAuthSession.binderDied();
+        waitForIdle();
+
+        assertNull(mBiometricService.mCurrentAuthSession);
+        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+        verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testClientBinderDied_whenAuthenticating() throws Exception {
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+
+        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+                true /* requireConfirmation */, null /* authenticators */);
+        waitForIdle();
+        verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession),
+                anyInt());
+
+        assertEquals(BiometricService.STATE_AUTH_STARTED,
+                mBiometricService.mCurrentAuthSession.mState);
+        mBiometricService.mCurrentAuthSession.binderDied();
+        waitForIdle();
+
+        assertNotNull(mBiometricService.mCurrentAuthSession);
+        verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
+        assertEquals(BiometricService.STATE_CLIENT_DIED_CANCELLING,
+                mBiometricService.mCurrentAuthSession.mState);
+
+        verify(mBiometricService.mAuthenticators.get(0).impl).cancelAuthenticationFromService(
+                any(),
+                any(),
+                anyInt(),
+                anyInt(),
+                anyInt(),
+                eq(false) /* fromClient */);
+
+        // Simulate ERROR_CANCELED received from HAL
+        mBiometricService.mInternalReceiver.onError(
+                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                BiometricAuthenticator.TYPE_FACE,
+                BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+                0 /* vendorCode */);
+        waitForIdle();
+        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+        verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
+        assertNull(mBiometricService.mCurrentAuthSession);
+    }
+
+    @Test
     public void testAuthenticate_credentialAllowedButNotSetup_returnsNoDeviceCredential()
             throws Exception {
         when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false);
@@ -311,7 +381,7 @@
                 eq(0 /* vendorCode */));
 
         // Enrolled, not disabled in settings, user requires confirmation in settings
-        resetReceiver();
+        resetReceivers();
         when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
         when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
                 .thenReturn(true);
@@ -332,7 +402,7 @@
                 anyInt() /* callingUserId */);
 
         // Enrolled, not disabled in settings, user doesn't require confirmation in settings
-        resetReceiver();
+        resetReceivers();
         when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
                 .thenReturn(false);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -1198,7 +1268,7 @@
                 eq(0) /* vendorCode */);
 
         // Request for weak auth works
-        resetReceiver();
+        resetReceivers();
         authenticators = Authenticators.BIOMETRIC_WEAK;
         assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
                 invokeCanAuthenticate(mBiometricService, authenticators));
@@ -1217,7 +1287,7 @@
                 anyInt() /* sysUiSessionId */);
 
         // Requesting strong and credential, when credential is setup
-        resetReceiver();
+        resetReceivers();
         authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
         when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
         assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
@@ -1244,7 +1314,7 @@
             }
         }
 
-        resetReceiver();
+        resetReceivers();
         authenticators = Authenticators.BIOMETRIC_STRONG;
         assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
                 invokeCanAuthenticate(mBiometricService, authenticators));
@@ -1449,9 +1519,12 @@
         }
     }
 
-    private void resetReceiver() {
+    private void resetReceivers() {
         mReceiver1 = mock(IBiometricServiceReceiver.class);
         mReceiver2 = mock(IBiometricServiceReceiver.class);
+
+        when(mReceiver1.asBinder()).thenReturn(mock(Binder.class));
+        when(mReceiver2.asBinder()).thenReturn(mock(Binder.class));
     }
 
     private void resetStatusBar() {