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

Commit 1ba3a42a authored by Kevin Chyn's avatar Kevin Chyn
Browse files

3/n: Add AuthSession#STATE_AUTH_PAUSED_RESUMING

Updated the "try again" logic to continue using the same AuthSession.
There's no need to create an entirely new one, since all of the
params are the same. When a pausable biometric enteres the
STATE_AUTH_PAUSED state, and the user taps "try again", we enter
the new STATE_AUTH_PAUSED_RESUMING state. New cookies are assigned,
and once all sensors have been returned, the sensors are started and
we go to the STATE_AUTH_STARTED state.

This change also moves most of the remaining AuthSession state
management to within itself.

Bug: 149067920

Test: atest com.android.server.biometrics
Change-Id: Iba80c44faefcf4c4d3f0c51aff635c0a47b9339f
parent b53472ab
Loading
Loading
Loading
Loading
+65 −28
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.os.RemoteException;
import android.security.KeyStore;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;

import java.lang.annotation.Retention;
@@ -69,6 +70,11 @@ final class AuthSession {
     * fingerprint.
     */
    static final int STATE_AUTH_PAUSED = 3;
    /**
     * Paused, but "try again" was pressed. Sensors have new cookies and we're now waiting for all
     * cookies to be returned.
     */
    static final int STATE_AUTH_PAUSED_RESUMING = 4;
    /**
     * Authentication is successful, but we're waiting for the user to press "confirm" button.
     */
@@ -90,6 +96,7 @@ final class AuthSession {
            STATE_AUTH_CALLED,
            STATE_AUTH_STARTED,
            STATE_AUTH_PAUSED,
            STATE_AUTH_PAUSED_RESUMING,
            STATE_AUTH_PENDING_CONFIRM,
            STATE_AUTHENTICATED_PENDING_SYSUI,
            STATE_ERROR_PENDING_SYSUI,
@@ -105,23 +112,18 @@ final class AuthSession {

    // The following variables are passed to authenticateInternal, which initiates the
    // appropriate <Biometric>Services.
    final IBinder mToken;
    final long mOperationId;
    final int mUserId;
    @VisibleForTesting final IBinder mToken;
    // Info to be shown on BiometricDialog when all cookies are returned.
    @VisibleForTesting final Bundle mBundle;
    private final long mOperationId;
    private final int mUserId;
    private final IBiometricSensorReceiver mSensorReceiver;
    // Original receiver from BiometricPrompt.
    final IBiometricServiceReceiver mClientReceiver;
    final String mOpPackageName;
    // Info to be shown on BiometricDialog when all cookies are returned.
    final Bundle mBundle;
    final int mCallingUid;
    final int mCallingPid;
    final int mCallingUserId;

    // True if this authentication session is still part of the same BiometricPrompt call. For
    // example, authentication can be in the paused state, in which a new AuthSession is created
    // even though the caller is still the same BiometricPrompt invocation.
    private final boolean mContinuing;
    private final IBiometricServiceReceiver mClientReceiver;
    private final String mOpPackageName;
    private final int mCallingUid;
    private final int mCallingPid;
    private final int mCallingUserId;

    // The current state, which can be either idle, called, or started
    private @SessionState int mState = STATE_AUTH_IDLE;
@@ -138,14 +140,13 @@ final class AuthSession {
    long mAuthenticatedTimeMs;

    AuthSession(IStatusBarService statusBarService, IBiometricSysuiReceiver sysuiReceiver,
            KeyStore keystore, boolean continuing, Random random, PreAuthInfo preAuthInfo,
            KeyStore keystore, Random random, PreAuthInfo preAuthInfo,
            IBinder token, long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
            IBiometricServiceReceiver clientReceiver, String opPackageName, Bundle bundle,
            int callingUid, int callingPid, int callingUserId) {
        mStatusBarService = statusBarService;
        mSysuiReceiver = sysuiReceiver;
        mKeyStore = keystore;
        mContinuing = continuing;
        mRandom = random;
        mPreAuthInfo = preAuthInfo;
        mToken = token;
@@ -180,6 +181,18 @@ final class AuthSession {
        }
    }

    private void setSensorsToStateWaitingForCookie() throws RemoteException {
        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
            final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
            final boolean requireConfirmation = sensor.confirmationSupported()
                    && (sensor.confirmationAlwaysRequired(mUserId)
                    || mPreAuthInfo.confirmationRequested);
            sensor.goToStateWaitingForCookie(requireConfirmation, mToken, mOperationId,
                    mUserId, mSensorReceiver, mOpPackageName, cookie, mCallingUid, mCallingPid,
                    mCallingUserId);
        }
    }

    void goToInitialState() throws RemoteException {
        if (mPreAuthInfo.credentialAvailable && mPreAuthInfo.eligibleSensors.isEmpty()) {
            // Only device credential should be shown. In this case, we don't need to wait,
@@ -197,16 +210,8 @@ final class AuthSession {
                    mOperationId);
        } else if (!mPreAuthInfo.eligibleSensors.isEmpty()) {
            // Some combination of biometric or biometric|credential is requested
            setSensorsToStateWaitingForCookie();
            mState = AuthSession.STATE_AUTH_CALLED;
            for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
                final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
                final boolean requireConfirmation = sensor.confirmationSupported()
                        && (sensor.confirmationAlwaysRequired(mUserId)
                        || mPreAuthInfo.confirmationRequested);
                sensor.goToStateWaitingForCookie(requireConfirmation, mToken, mOperationId,
                        mUserId, mSensorReceiver, mOpPackageName, cookie, mCallingUid, mCallingPid,
                        mCallingUserId);
            }
        } else {
            // No authenticators requested. This should never happen - an exception should have
            // been thrown earlier in the pipeline.
@@ -221,10 +226,10 @@ final class AuthSession {

        if (allCookiesReceived()) {
            mStartTimeMs = System.currentTimeMillis();
            mState = STATE_AUTH_STARTED;
            startAllPreparedSensors();

            if (!mContinuing) {
            // No need to request the UI if we're coming from the paused state
            if (mState != STATE_AUTH_PAUSED_RESUMING) {
                try {
                    final @BiometricAuthenticator.Modality int modality =
                            getEligibleModalities();
@@ -239,6 +244,7 @@ final class AuthSession {
                    Slog.e(TAG, "Remote exception", e);
                }
            }
            mState = STATE_AUTH_STARTED;
        }
    }

@@ -373,6 +379,33 @@ final class AuthSession {
        }
    }

    void onSystemEvent(int event) {
        final boolean shouldReceive = mBundle
                .getBoolean(BiometricPrompt.KEY_RECEIVE_SYSTEM_EVENTS, false);
        if (!shouldReceive) {
            return;
        }

        try {
            mClientReceiver.onSystemEvent(event);
        } catch (RemoteException e) {
            Slog.e(TAG, "RemoteException", e);
        }
    }

    void onTryAgainPressed() {
        if (mState != STATE_AUTH_PAUSED) {
            Slog.w(TAG, "onTryAgainPressed, state: " + mState);
        }

        try {
            setSensorsToStateWaitingForCookie();
            mState = STATE_AUTH_PAUSED_RESUMING;
        } catch (RemoteException e) {
            Slog.e(TAG, "RemoteException: " + e);
        }
    }

    void onAuthenticationSucceeded(int sensorId, boolean requireConfirmation, boolean strong,
            byte[] token) {
        if (strong) {
@@ -559,6 +592,10 @@ final class AuthSession {
        return mState;
    }

    int getUserId() {
        return mUserId;
    }

    @Override
    public String toString() {
        return "State: " + mState
+21 −38
Original line number Diff line number Diff line
@@ -859,7 +859,7 @@ public class BiometricService extends SystemService {

            if (LoggableMonitor.DEBUG) {
                Slog.v(LoggableMonitor.TAG, "Confirmed! Modality: " + statsModality()
                        + ", User: " + mCurrentAuthSession.mUserId
                        + ", User: " + mCurrentAuthSession.getUserId()
                        + ", IsCrypto: " + mCurrentAuthSession.isCrypto()
                        + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
                        + ", RequireConfirmation: "
@@ -870,13 +870,13 @@ public class BiometricService extends SystemService {

            FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
                    statsModality(),
                    mCurrentAuthSession.mUserId,
                    mCurrentAuthSession.getUserId(),
                    mCurrentAuthSession.isCrypto(),
                    BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
                    mCurrentAuthSession.mPreAuthInfo.confirmationRequested,
                    FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
                    latency,
                    mInjector.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId));
                    mInjector.isDebugEnabled(getContext(), mCurrentAuthSession.getUserId()));
        } else {
            final long latency = System.currentTimeMillis() - mCurrentAuthSession.mStartTimeMs;

@@ -887,7 +887,7 @@ public class BiometricService extends SystemService {
                            : 0;
            if (LoggableMonitor.DEBUG) {
                Slog.v(LoggableMonitor.TAG, "Dismissed! Modality: " + statsModality()
                        + ", User: " + mCurrentAuthSession.mUserId
                        + ", User: " + mCurrentAuthSession.getUserId()
                        + ", IsCrypto: " + mCurrentAuthSession.isCrypto()
                        + ", Action: " + BiometricsProtoEnums.ACTION_AUTHENTICATE
                        + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
@@ -897,13 +897,13 @@ public class BiometricService extends SystemService {
            // Auth canceled
            FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
                    statsModality(),
                    mCurrentAuthSession.mUserId,
                    mCurrentAuthSession.getUserId(),
                    mCurrentAuthSession.isCrypto(),
                    BiometricsProtoEnums.ACTION_AUTHENTICATE,
                    BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
                    error,
                    0 /* vendorCode */,
                    mInjector.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId),
                    mInjector.isDebugEnabled(getContext(), mCurrentAuthSession.getUserId()),
                    latency);
        }
    }
@@ -1019,17 +1019,12 @@ public class BiometricService extends SystemService {
        Slog.d(TAG, "onTryAgainPressed");
        // No need to check permission, since it can only be invoked by SystemUI
        // (or system server itself).
        authenticateInternal(true /* continuing */,
                mCurrentAuthSession.mToken,
                mCurrentAuthSession.mOperationId,
                mCurrentAuthSession.mUserId,
                mCurrentAuthSession.mClientReceiver,
                mCurrentAuthSession.mOpPackageName,
                mCurrentAuthSession.mBundle,
                mCurrentAuthSession.mCallingUid,
                mCurrentAuthSession.mCallingPid,
                mCurrentAuthSession.mCallingUserId,
                mCurrentAuthSession.mPreAuthInfo);
        if (mCurrentAuthSession == null) {
            Slog.e(TAG, "handleOnTryAgainPressed: AuthSession is null");
            return;
        }

        mCurrentAuthSession.onTryAgainPressed();
    }

    private void handleOnDeviceCredentialPressed() {
@@ -1043,24 +1038,14 @@ public class BiometricService extends SystemService {
    }

    private void handleOnSystemEvent(int event) {
        final boolean shouldReceive = mCurrentAuthSession.mBundle
                .getBoolean(BiometricPrompt.KEY_RECEIVE_SYSTEM_EVENTS, false);
        Slog.d(TAG, "onSystemEvent: " + event + ", shouldReceive: " + shouldReceive);
        Slog.d(TAG, "onSystemEvent: " + event);

        if (mCurrentAuthSession == null) {
            Slog.e(TAG, "handleOnSystemEvent: AuthSession is null");
            return;
        }

        if (!shouldReceive) {
            return;
        }

        try {
            mCurrentAuthSession.mClientReceiver.onSystemEvent(event);
        } catch (RemoteException e) {
            Slog.e(TAG, "RemoteException", e);
        }
        mCurrentAuthSession.onSystemEvent(event);
    }

    /**
@@ -1109,7 +1094,7 @@ public class BiometricService extends SystemService {
                                Authenticators.DEVICE_CREDENTIAL);
                    }

                    authenticateInternal(false /* continuing */, token, operationId, userId,
                    authenticateInternal(token, operationId, userId,
                            receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
                            preAuthInfo);
                } else {
@@ -1132,15 +1117,14 @@ public class BiometricService extends SystemService {
     * 2) Preparing <Biometric>Services for authentication when BiometricPrompt is already shown
     * and the user has pressed "try again"
     */
    private void authenticateInternal(boolean continuing, IBinder token, long operationId,
    private void authenticateInternal(IBinder token, long operationId,
            int userId, IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
            int callingUid, int callingPid, int callingUserId, PreAuthInfo preAuthInfo) {
        Slog.d(TAG, "Creating authSession with authRequest: " + preAuthInfo
                + ", continuing: " + continuing);
        Slog.d(TAG, "Creating authSession with authRequest: " + preAuthInfo);

        // No need to dismiss dialog / send error yet if we're continuing authentication, e.g.
        // "Try again" is showing due to something like ERROR_TIMEOUT.
        if (mCurrentAuthSession != null && !continuing) {
        if (mCurrentAuthSession != null) {
            // Forcefully cancel authentication. Dismiss the UI, and immediately send
            // ERROR_CANCELED to the client. Note that we should/will ignore HAL ERROR_CANCELED.
            // Expect to see some harmless "unknown cookie" errors.
@@ -1149,10 +1133,9 @@ public class BiometricService extends SystemService {
            mCurrentAuthSession = null;
        }

        mCurrentAuthSession = new AuthSession(mStatusBarService, mSysuiReceiver, mKeyStore,
                continuing, mRandom, preAuthInfo, token, operationId, userId,
                mBiometricSensorReceiver, receiver, opPackageName, bundle, callingUid, callingPid,
                callingUserId);
        mCurrentAuthSession = new AuthSession(mStatusBarService, mSysuiReceiver, mKeyStore, mRandom,
                preAuthInfo, token, operationId, userId, mBiometricSensorReceiver, receiver,
                opPackageName, bundle, callingUid, callingPid, callingUserId);
        try {
            mCurrentAuthSession.goToInitialState();
        } catch (RemoteException e) {
+4 −6
Original line number Diff line number Diff line
@@ -92,8 +92,7 @@ public class AuthSessionTest {
        setupFingerprint(0 /* id */);
        setupFace(1 /* id */, false /* confirmationAlwaysRequired */);

        final AuthSession session = createAuthSession(false /* continuing */,
                mSensors,
        final AuthSession session = createAuthSession(mSensors,
                false /* checkDevicePolicyManager */,
                Authenticators.BIOMETRIC_STRONG,
                0 /* operationId */,
@@ -120,8 +119,7 @@ public class AuthSessionTest {
        final int callingPid = 1000;
        final int callingUserId = 10000;

        final AuthSession session = createAuthSession(false /* continuing */,
                mSensors,
        final AuthSession session = createAuthSession(mSensors,
                false /* checkDevicePolicyManager */,
                Authenticators.BIOMETRIC_STRONG,
                operationId,
@@ -188,7 +186,7 @@ public class AuthSessionTest {
                checkDevicePolicyManager);
    }

    private AuthSession createAuthSession(boolean continuing, List<BiometricSensor> sensors,
    private AuthSession createAuthSession(List<BiometricSensor> sensors,
            boolean checkDevicePolicyManager, @Authenticators.Types int authenticators,
            long operationId, int userId,
            int callingUid, int callingPid, int callingUserId)
@@ -199,7 +197,7 @@ public class AuthSessionTest {
        final PreAuthInfo preAuthInfo = createPreAuthInfo(sensors, userId, bundle,
                checkDevicePolicyManager);

        return new AuthSession(mStatusBarService, mSysuiReceiver, mKeyStore, continuing,
        return new AuthSession(mStatusBarService, mSysuiReceiver, mKeyStore,
                mRandom, preAuthInfo, mToken, operationId, userId, mSensorReceiver,
                mClientReceiver, TEST_PACKAGE, bundle, callingUid,
                callingPid, callingUserId);
+3 −3
Original line number Diff line number Diff line
@@ -616,13 +616,13 @@ public class BiometricServiceTest {
        assertEquals(AuthSession.STATE_AUTH_PAUSED,
                mBiometricService.mCurrentAuthSession.getState());

        // Pressing "Try again" on SystemUI starts a new auth session.
        // Pressing "Try again" on SystemUI
        mBiometricService.mSysuiReceiver.onTryAgainPressed();
        waitForIdle();
        verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());

        // New one has been created
        assertEquals(AuthSession.STATE_AUTH_CALLED,
        // AuthSession is now resuming
        assertEquals(AuthSession.STATE_AUTH_PAUSED_RESUMING,
                mBiometricService.mCurrentAuthSession.getState());

        // Test resuming when hardware becomes ready. SystemUI should not be requested to