diff options
4 files changed, 149 insertions, 15 deletions
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index 5d334c22f2db..edc8f15a9a03 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 @@ public abstract class AuthenticationClient extends ClientMonitor { @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 7e28e94a17bb..4ddfe1b6e2d2 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 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { * 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 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { break; } + case MSG_CLIENT_DIED: { + handleClientDied(); + break; + } + default: Slog.e(TAG, "Unknown message: " + msg); break; @@ -1391,6 +1415,7 @@ public class BiometricService extends SystemService { } 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 @@ public class BiometricService extends SystemService { } 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 @@ public class BiometricService extends SystemService { } } + 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 @@ public class BiometricService extends SystemService { 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 942e0501d88d..b02969524221 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 @@ public abstract class ClientMonitor extends LoggableMonitor implements IBinder.D @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 285caf34ae67..48ec529c9a45 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 class BiometricServiceTest { 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 @@ public class BiometricServiceTest { } @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 @@ public class BiometricServiceTest { 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 @@ public class BiometricServiceTest { 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 @@ public class BiometricServiceTest { 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 @@ public class BiometricServiceTest { 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 @@ public class BiometricServiceTest { } } - resetReceiver(); + resetReceivers(); authenticators = Authenticators.BIOMETRIC_STRONG; assertEquals(BiometricManager.BIOMETRIC_SUCCESS, invokeCanAuthenticate(mBiometricService, authenticators)); @@ -1449,9 +1519,12 @@ public class BiometricServiceTest { } } - 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() { |