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

Commit eab24acd authored by Joe Bolinger's avatar Joe Bolinger
Browse files

Run biometric cleanup on user switch.

This will force the new to user start on the scheduler, open a new session, and close the old user's session in the HAL.

Add a similar user swtich hook to keyguard to refresh the lockout state. Note that lockout behavior is still ill-defined (b/225986544) and HALs may not support a lockout state per user.

Bug: 205908931
Test: manual (unlock, cause lockout, & switch users)
Test: atest KeyguardUpdateMonitorTest

Change-Id: I89d3d7454894bc5caa0cac33e96d7fd0d61e9083
parent 82c6e424
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -268,4 +268,27 @@ public interface BiometricConstants {
     */
    int BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL = 1;

    /**
     * No lockout.
     * @hide
     */
    int BIOMETRIC_LOCKOUT_NONE = 0;
    /**
     * The biometric is in a temporary lockout state that will expire after some time.
     * @hide
     */
    int BIOMETRIC_LOCKOUT_TIMED = 1;
    /**
     * The biometric is locked out until a reset occurs. Resets are typically triggered by
     * successfully authenticating via a stronger method than the one that is locked out.
     * @hide
     */
    int BIOMETRIC_LOCKOUT_PERMANENT = 2;

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({BIOMETRIC_LOCKOUT_NONE, BIOMETRIC_LOCKOUT_TIMED, BIOMETRIC_LOCKOUT_PERMANENT})
    @interface LockoutMode {}
}
+17 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.hardware.face;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -672,6 +673,22 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
        return new ArrayList<>();
    }

    /**
     * @hide
     */
    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
    @BiometricConstants.LockoutMode
    public int getLockoutModeForUser(int sensorId, int userId) {
        if (mService != null) {
            try {
                return mService.getLockoutModeForUser(sensorId, userId);
            } catch (RemoteException e) {
                e.rethrowFromSystemServer();
            }
        }
        return BIOMETRIC_LOCKOUT_NONE;
    }

    /**
     * @hide
     */
+18 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;

import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE;
@@ -41,6 +42,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricTestSession;
@@ -1083,6 +1085,22 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
        }
    }

    /**
     * @hide
     */
    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
    @BiometricConstants.LockoutMode
    public int getLockoutModeForUser(int sensorId, int userId) {
        if (mService != null) {
            try {
                return mService.getLockoutModeForUser(sensorId, userId);
            } catch (RemoteException e) {
                e.rethrowFromSystemServer();
            }
        }
        return BIOMETRIC_LOCKOUT_NONE;
    }

    /**
     * @hide
     */
+45 −16
Original line number Diff line number Diff line
@@ -21,6 +21,10 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.ACTION_USER_REMOVED;
import static android.content.Intent.ACTION_USER_STOPPED;
import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
import static android.hardware.biometrics.BiometricConstants.LockoutMode;
import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;

import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
@@ -61,6 +65,7 @@ import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.CancellationSignal;
@@ -864,10 +869,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
        }
    }

    private void handleFingerprintLockoutReset() {
        boolean changed = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
        mFingerprintLockedOut = false;
        mFingerprintLockedOutPermanent = false;
    private void handleFingerprintLockoutReset(@LockoutMode int mode) {
        Log.d(TAG, "handleFingerprintLockoutReset: " + mode);
        final boolean wasLockout = mFingerprintLockedOut;
        final boolean wasLockoutPermanent = mFingerprintLockedOutPermanent;
        mFingerprintLockedOut = (mode == BIOMETRIC_LOCKOUT_TIMED)
                || mode == BIOMETRIC_LOCKOUT_PERMANENT;
        mFingerprintLockedOutPermanent = (mode == BIOMETRIC_LOCKOUT_PERMANENT);
        final boolean changed = (mFingerprintLockedOut != wasLockout)
                || (mFingerprintLockedOutPermanent != wasLockoutPermanent);

        if (isUdfpsEnrolled()) {
            // TODO(b/194825098): update the reset signal(s)
@@ -877,7 +887,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
            // be noticeable.
            mHandler.postDelayed(() -> {
                updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
            }, BIOMETRIC_LOCKOUT_RESET_DELAY_MS);
            }, getBiometricLockoutDelay());
        } else {
            updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
        }
@@ -1075,13 +1085,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
        }
    }

    private void handleFaceLockoutReset() {
        boolean changed = mFaceLockedOutPermanent;
        mFaceLockedOutPermanent = false;
    private void handleFaceLockoutReset(@LockoutMode int mode) {
        Log.d(TAG, "handleFaceLockoutReset: " + mode);
        final boolean wasLockoutPermanent = mFaceLockedOutPermanent;
        mFaceLockedOutPermanent = (mode == BIOMETRIC_LOCKOUT_PERMANENT);
        final boolean changed = (mFaceLockedOutPermanent != wasLockoutPermanent);

        mHandler.postDelayed(() -> {
            updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
        }, BIOMETRIC_LOCKOUT_RESET_DELAY_MS);
        }, getBiometricLockoutDelay());

        if (changed) {
            notifyLockedOutStateChanged(BiometricSourceType.FACE);
@@ -1461,7 +1473,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
            = new FingerprintManager.LockoutResetCallback() {
        @Override
        public void onLockoutReset(int sensorId) {
            handleFingerprintLockoutReset();
            handleFingerprintLockoutReset(BIOMETRIC_LOCKOUT_NONE);
        }
    };

@@ -1469,7 +1481,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
            = new FaceManager.LockoutResetCallback() {
        @Override
        public void onLockoutReset(int sensorId) {
            handleFaceLockoutReset();
            handleFaceLockoutReset(BIOMETRIC_LOCKOUT_NONE);
        }
    };

@@ -1579,10 +1591,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
                }
    };

    private CancellationSignal mFingerprintCancelSignal;
    private CancellationSignal mFaceCancelSignal;
    @VisibleForTesting
    CancellationSignal mFingerprintCancelSignal;
    @VisibleForTesting
    CancellationSignal mFaceCancelSignal;
    private FingerprintManager mFpm;
    private FaceManager mFaceManager;
    private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
    private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
    private boolean mFingerprintLockedOut;
    private boolean mFingerprintLockedOutPermanent;
@@ -2017,6 +2032,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab

        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
            mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
            mFingerprintSensorProperties = mFpm.getSensorPropertiesInternal();
        }
        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
            mFaceManager = (FaceManager) context.getSystemService(Context.FACE_SERVICE);
@@ -2381,11 +2397,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
        final boolean shouldListenUdfpsState = !isUdfps
                || (!userCanSkipBouncer
                    && !isEncryptedOrLockdownForUser
                    && userDoesNotHaveTrust
                    && !mFingerprintLockedOut);
                    && userDoesNotHaveTrust);

        final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
                && shouldListenBouncerState && shouldListenUdfpsState;
                && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();

        if (DEBUG_FINGERPRINT || DEBUG_SPEW) {
            maybeLogListenerModelData(
@@ -2771,6 +2786,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
                cb.onUserSwitchComplete(userId);
            }
        }

        if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
            handleFaceLockoutReset(mFaceManager.getLockoutModeForUser(
                    mFaceSensorProperties.get(0).sensorId, userId));
        }
        if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) {
            handleFingerprintLockoutReset(mFpm.getLockoutModeForUser(
                    mFingerprintSensorProperties.get(0).sensorId, userId));
        }

        mInteractionJankMonitor.end(InteractionJankMonitor.CUJ_USER_SWITCH);
        mLatencyTracker.onActionEnd(LatencyTracker.ACTION_USER_SWITCH);
    }
@@ -3480,6 +3505,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
        }
    }

    protected int getBiometricLockoutDelay() {
        return BIOMETRIC_LOCKOUT_RESET_DELAY_MS;
    }

    /**
     * Unregister all listeners.
     */
+83 −1
Original line number Diff line number Diff line
@@ -35,10 +35,12 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
@@ -50,15 +52,20 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.UserHandle;
@@ -121,6 +128,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
            TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
            DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID,
            TEST_CARRIER_ID, 0);
    private static final int FACE_SENSOR_ID = 0;
    private static final int FINGERPRINT_SENSOR_ID = 1;

    @Mock
    private DumpManager mDumpManager;
    @Mock
@@ -212,6 +222,16 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {

        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
        when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of(
                new FingerprintSensorPropertiesInternal(1 /* sensorId */,
                        FingerprintSensorProperties.STRENGTH_STRONG,
                        1 /* maxEnrollmentsPerUser */,
                        List.of(new ComponentInfoInternal("fingerprintSensor" /* componentId */,
                                "vendor/model/revision" /* hardwareVersion */,
                                "1.01" /* firmwareVersion */,
                                "00000001" /* serialNumber */, "" /* softwareVersion */)),
                        FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
                        false /* resetLockoutRequiresHAT */)));
        when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
        when(mUserManager.isPrimaryUser()).thenReturn(true);
        when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class));
@@ -232,9 +252,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
        mSpiedContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);

        mMockitoSession = ExtendedMockito.mockitoSession()
                .spyStatic(SubscriptionManager.class).startMocking();
                .spyStatic(SubscriptionManager.class)
                .spyStatic(ActivityManager.class)
                .startMocking();
        ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                .when(SubscriptionManager::getDefaultSubscriptionId);
        ExtendedMockito.doReturn(KeyguardUpdateMonitor.getCurrentUser())
                .when(ActivityManager::getCurrentUser);

        mTestableLooper = TestableLooper.get(this);
        allowTestableLooperAsMainThread();
@@ -756,6 +780,59 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
        verify(mLatencyTracker).onActionEnd(LatencyTracker.ACTION_USER_SWITCH);
    }

    @Test
    public void testMultiUserLockoutChanged_whenUserSwitches() {
        testMultiUserLockout_whenUserSwitches(BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT,
                BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT);
    }

    @Test
    public void testMultiUserLockoutNotChanged_whenUserSwitches() {
        testMultiUserLockout_whenUserSwitches(BiometricConstants.BIOMETRIC_LOCKOUT_NONE,
                BiometricConstants.BIOMETRIC_LOCKOUT_NONE);
    }

    private void testMultiUserLockout_whenUserSwitches(
            @BiometricConstants.LockoutMode int fingerprintLockoutMode,
            @BiometricConstants.LockoutMode int faceLockoutMode) {
        final int newUser = 12;
        final boolean faceLocked =
                faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
        final boolean fpLocked =
                fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
        when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
                .thenReturn(fingerprintLockoutMode);
        when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
                .thenReturn(faceLockoutMode);

        mKeyguardUpdateMonitor.dispatchStartedWakingUp();
        mTestableLooper.processAllMessages();
        mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);

        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
        verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
                anyInt());

        final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
        final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
        mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
        mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel;
        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
        mKeyguardUpdateMonitor.registerCallback(callback);

        mKeyguardUpdateMonitor.handleUserSwitchComplete(newUser);
        mTestableLooper.processAllMessages();

        verify(faceCancel, faceLocked ? times(1) : never()).cancel();
        verify(fpCancel, fpLocked ? times(1) : never()).cancel();
        verify(callback, faceLocked ? times(1) : never()).onBiometricRunningStateChanged(
                eq(false), eq(BiometricSourceType.FACE));
        verify(callback, fpLocked ? times(1) : never()).onBiometricRunningStateChanged(
                eq(false), eq(BiometricSourceType.FINGERPRINT));
        assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked);
        assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked);
    }

    @Test
    public void testGetUserCanSkipBouncer_whenTrust() {
        int user = KeyguardUpdateMonitor.getCurrentUser();
@@ -1115,5 +1192,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
            mSimStateChanged.set(true);
            super.handleSimStateChange(subId, slotId, state);
        }

        @Override
        protected int getBiometricLockoutDelay() {
            return 0;
        }
    }
}
Loading