Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2291072b authored by Kevin Chyn's avatar Kevin Chyn
Browse files

Ensure that cancelling authentication ends up in the correct state

When authentication is canceled from the caller (not by the system),
BiometricService should do the following
1) Inform SystemUI to hide the dialog
2) Send the onError callback to the calling application

SystemUI does not need to send any callbacks (avoid round trip)

Bug: 146234687

Test: com.android.server.biometrics
Test: com.android.systemui.biometrics

Change-Id: Icd3e07d4921870e1e63e125c48caaaa759af2d36
parent e8d2d1f9
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -341,6 +341,10 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
        if (DEBUG) Log.d(TAG, "hideAuthenticationDialog");

        mCurrentDialog.dismissFromSystemServer();

        // BiometricService will have already sent the callback to the client in this case.
        // This avoids a round trip to SystemUI. So, just dismiss the dialog and we're done.
        mCurrentDialog = null;
    }

    private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
+4 −10
Original line number Diff line number Diff line
@@ -279,20 +279,14 @@ public class AuthControllerTest extends SysuiTestCase {
    }

    @Test
    public void testDismissWithoutCallbackInvoked_whenSystemRequested() {
    public void testHideAuthenticationDialog_invokesDismissFromSystemServer() {
        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
        mAuthController.hideAuthenticationDialog();
        verify(mDialog1).dismissFromSystemServer();
    }

    @Test
    public void testClientNotified_whenDismissedBySystemServer() {
        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
        mAuthController.hideAuthenticationDialog();
        verify(mDialog1).dismissFromSystemServer();

        assertNotNull(mAuthController.mCurrentDialog);
        assertNotNull(mAuthController.mReceiver);
        // In this case, BiometricService sends the error to the client immediately, without
        // doing a round trip to SystemUI.
        assertNull(mAuthController.mCurrentDialog);
    }

    // Corner case tests
+8 −5
Original line number Diff line number Diff line
@@ -1240,14 +1240,17 @@ public class BiometricService extends SystemService {
                        // SystemUI handles transition from biometric to device credential.
                        mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
                        mStatusBarService.onBiometricError(modality, error, vendorCode);
                    } else {
                        mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI;
                        if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
                    } else if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
                        mStatusBarService.hideAuthenticationDialog();
                        // TODO: If multiple authenticators are simultaneously running, this will
                        // need to be modified. Send the error to the client here, instead of doing
                        // a round trip to SystemUI.
                        mCurrentAuthSession.mClientReceiver.onError(modality, error, vendorCode);
                        mCurrentAuthSession = null;
                    } else {
                        mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI;
                        mStatusBarService.onBiometricError(modality, error, vendorCode);
                    }
                    }
                } else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) {
                    // In the "try again" state, we should forward canceled errors to
                    // the client and and clean up. The only error we should get here is
+23 −43
Original line number Diff line number Diff line
@@ -497,49 +497,6 @@ public class BiometricServiceTest {
                BiometricService.STATE_AUTH_STARTED);
    }

    @Test
    public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws
            Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                false /* requireConfirmation */, null /* authenticators */);

        // Create a new pending auth session but don't start it yet. HAL contract is that previous
        // one must get ERROR_CANCELED. Simulate that here by creating the pending auth session,
        // sending ERROR_CANCELED to the current auth session, and then having the second one
        // onReadyForAuthentication.
        invokeAuthenticate(mBiometricService.mImpl, mReceiver2, false /* requireConfirmation */,
                null /* authenticators */);
        waitForIdle();

        assertEquals(mBiometricService.mCurrentAuthSession.mState,
                BiometricService.STATE_AUTH_STARTED);
        mBiometricService.mInternalReceiver.onError(
                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
                BiometricAuthenticator.TYPE_FINGERPRINT,
                BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
        waitForIdle();

        // Auth session doesn't become null until SystemUI responds that the animation is completed
        assertNotNull(mBiometricService.mCurrentAuthSession);
        // ERROR_CANCELED is not sent until SystemUI responded that animation is completed
        verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
        verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());

        // SystemUI dialog closed
        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();

        // After SystemUI notifies that the animation has completed
        mBiometricService.mInternalReceiver
                .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
        waitForIdle();
        verify(mReceiver1).onError(
                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
                eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
                eq(0 /* vendorCode */));
        assertNull(mBiometricService.mCurrentAuthSession);
    }

    @Test
    public void testErrorHalTimeout_whenAuthenticating_entersPausedState() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
@@ -959,6 +916,29 @@ public class BiometricServiceTest {
                BiometricService.STATE_AUTH_STARTED);
    }

    @Test
    public void testCancel_whenAuthenticating() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                false /* requireConfirmation */, null /* authenticators */);

        mBiometricService.mImpl.cancelAuthentication(mBiometricService.mCurrentAuthSession.mToken,
                TEST_PACKAGE_NAME);
        waitForIdle();

        // Pretend that the HAL has responded to cancel with ERROR_CANCELED
        mBiometricService.mInternalReceiver.onError(getCookieForCurrentSession(
                mBiometricService.mCurrentAuthSession), BiometricAuthenticator.TYPE_FINGERPRINT,
                BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
        waitForIdle();

        // Hides system dialog and invokes the onError callback
        verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT),
                eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
                eq(0 /* vendorCode */));
        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
    }

    @Test
    public void testCanAuthenticate_whenDeviceHasRequestedBiometricStrength() throws Exception {
        // When only biometric is requested, and sensor is strong enough