Merge "Run biometric cleanup on user switch." into tm-dev

This commit is contained in:
Joe Bolinger 2022-04-02 00:01:38 +00:00 committed by Android (Google) Code Review
commit 3bc43d4396
9 changed files with 227 additions and 20 deletions

View File

@ -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 {}
}

View File

@ -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
*/

View File

@ -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;
@ -1094,6 +1096,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
*/

View File

@ -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;
@ -865,10 +870,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)
@ -878,7 +888,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);
}
@ -1076,13 +1086,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);
@ -1462,7 +1474,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
= new FingerprintManager.LockoutResetCallback() {
@Override
public void onLockoutReset(int sensorId) {
handleFingerprintLockoutReset();
handleFingerprintLockoutReset(BIOMETRIC_LOCKOUT_NONE);
}
};
@ -1470,7 +1482,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
= new FaceManager.LockoutResetCallback() {
@Override
public void onLockoutReset(int sensorId) {
handleFaceLockoutReset();
handleFaceLockoutReset(BIOMETRIC_LOCKOUT_NONE);
}
};
@ -1580,10 +1592,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;
@ -2018,6 +2033,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);
@ -2382,11 +2398,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(
@ -2773,6 +2788,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);
}
@ -3508,6 +3533,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
protected int getBiometricLockoutDelay() {
return BIOMETRIC_LOCKOUT_RESET_DELAY_MS;
}
/**
* Unregister all listeners.
*/

View File

@ -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();
@ -765,6 +789,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();
@ -1124,5 +1201,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mSimStateChanged.set(true);
super.handleSimStateChange(subId, slotId, state);
}
@Override
protected int getBiometricLockoutDelay() {
return 0;
}
}
}

View File

@ -16,12 +16,14 @@
package com.android.server.biometrics.sensors;
import android.util.Slog;
import android.util.SparseIntArray;
/**
* For a single sensor, caches lockout states for all users.
*/
public class LockoutCache implements LockoutTracker {
private static final String TAG = "LockoutCache";
// Map of userId to LockoutMode
private final SparseIntArray mUserLockoutStates;
@ -31,6 +33,7 @@ public class LockoutCache implements LockoutTracker {
}
public void setLockoutModeForUser(int userId, @LockoutMode int mode) {
Slog.d(TAG, "Lockout for user: " + userId + " is " + mode);
synchronized (this) {
mUserLockoutStates.put(userId, mode);
}

View File

@ -17,6 +17,7 @@
package com.android.server.biometrics.sensors;
import android.annotation.IntDef;
import android.hardware.biometrics.BiometricConstants;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -25,9 +26,9 @@ import java.lang.annotation.RetentionPolicy;
* Interface for retrieval of current user's lockout state.
*/
public interface LockoutTracker {
int LOCKOUT_NONE = 0;
int LOCKOUT_TIMED = 1;
int LOCKOUT_PERMANENT = 2;
int LOCKOUT_NONE = BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
int LOCKOUT_TIMED = BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
int LOCKOUT_PERMANENT = BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT;
@Retention(RetentionPolicy.SOURCE)
@IntDef({LOCKOUT_NONE, LOCKOUT_TIMED, LOCKOUT_PERMANENT})

View File

@ -18,6 +18,9 @@ package com.android.server.biometrics.sensors.face.aidl;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.SynchronousUserSwitchObserver;
import android.app.UserSwitchObserver;
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
@ -91,6 +94,14 @@ public class Sensor {
@NonNull private final Supplier<AidlSession> mLazySession;
@Nullable private AidlSession mCurrentSession;
private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
@Override
public void onUserSwitching(int newUserId) {
mProvider.scheduleInternalCleanup(
mSensorProperties.sensorId, newUserId, null /* callback */);
}
};
@VisibleForTesting
public static class HalSessionCallback extends ISessionCallback.Stub {
/**
@ -537,6 +548,12 @@ public class Sensor {
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, mTag);
} catch (RemoteException e) {
Slog.e(mTag, "Unable to register user switch observer");
}
}
@NonNull Supplier<AidlSession> getLazySession() {

View File

@ -18,6 +18,9 @@ package com.android.server.biometrics.sensors.fingerprint.aidl;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.SynchronousUserSwitchObserver;
import android.app.UserSwitchObserver;
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
@ -92,6 +95,14 @@ public class Sensor {
@Nullable private AidlSession mCurrentSession;
@NonNull private final Supplier<AidlSession> mLazySession;
private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
@Override
public void onUserSwitching(int newUserId) {
mProvider.scheduleInternalCleanup(
mSensorProperties.sensorId, newUserId, null /* callback */);
}
};
@VisibleForTesting
public static class HalSessionCallback extends ISessionCallback.Stub {
@ -491,6 +502,12 @@ public class Sensor {
});
mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, mTag);
} catch (RemoteException e) {
Slog.e(mTag, "Unable to register user switch observer");
}
}
@NonNull Supplier<AidlSession> getLazySession() {