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() {