Implement SFPS “require screen on to auth”

Creates new setting on SFPS to require screen on before unlocking a
device. Also sets up toggles for this setting at the end of fingerprint
enrollment and on the fingerprint settings page, and adds test to verify
expected behavior.

Test: atest KeyguardUpdateMonitorTest#testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled
Fixes: 249169615
Fixes: 245343077
Fixes: 248530806
Change-Id: Ibc55dfa6667ec281add11a2665fceb073974ccb0
This commit is contained in:
Grace Cheng
2022-09-29 22:04:00 +00:00
parent 0b55b6f366
commit 9e07dafc6b
10 changed files with 207 additions and 3 deletions

View File

@@ -9858,6 +9858,13 @@ public final class Settings {
public static final String FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME =
"fingerprint_side_fps_auth_downtime";
/**
* Whether or not a SFPS device is required to be interactive for auth to unlock the device.
* @hide
*/
public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED =
"sfps_require_screen_on_to_auth_enabled";
/**
* Whether or not debugging is enabled.
* @hide

View File

@@ -4945,6 +4945,11 @@
<!-- If face auth sends the user directly to home/last open app, or stays on keyguard -->
<bool name="config_faceAuthDismissesKeyguard">true</bool>
<!-- Default value for whether a SFPS device is required to be
{@link KeyguardUpdateMonitor#isDeviceInteractive()} for fingerprint auth
to unlock the device. -->
<bool name="config_requireScreenOnToAuthEnabled">false</bool>
<!-- The component name for the default profile supervisor, which can be set as a profile owner
even after user setup is complete. The defined component should be used for supervision purposes
only. The component must be part of a system app. -->

View File

@@ -2725,6 +2725,7 @@
<java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" />
<java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" />
<java-symbol type="bool" name="config_faceAuthDismissesKeyguard" />
<java-symbol type="bool" name="config_requireScreenOnToAuthEnabled" />
<!-- Face config -->
<java-symbol type="integer" name="config_faceMaxTemplatesPerUser" />

View File

@@ -122,6 +122,7 @@ public class SecureSettings {
Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,

View File

@@ -177,6 +177,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);

View File

@@ -35,6 +35,7 @@ data class KeyguardFingerprintListenModel(
val keyguardOccluded: Boolean,
val occludingAppRequestingFp: Boolean,
val primaryUser: Boolean,
val shouldListenSfpsState: Boolean,
val shouldListenForFingerprintAssistant: Boolean,
val switchingUser: Boolean,
val udfps: Boolean,

View File

@@ -150,6 +150,7 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.Assert;
import com.android.systemui.util.settings.SecureSettings;
import com.google.android.collect.Lists;
@@ -321,17 +322,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
mCallbacks = Lists.newArrayList();
private ContentObserver mDeviceProvisionedObserver;
private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver;
private final ContentObserver mTimeFormatChangeObserver;
private boolean mSwitchingUser;
private boolean mDeviceInteractive;
private boolean mSfpsRequireScreenOnToAuthPrefEnabled;
private final SubscriptionManager mSubscriptionManager;
private final TelephonyListenerManager mTelephonyListenerManager;
private final TrustManager mTrustManager;
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final SecureSettings mSecureSettings;
private final InteractionJankMonitor mInteractionJankMonitor;
private final LatencyTracker mLatencyTracker;
private final StatusBarStateController mStatusBarStateController;
@@ -1930,6 +1934,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Context context,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
SecureSettings secureSettings,
DumpManager dumpManager,
@Background Executor backgroundExecutor,
@Main Executor mainExecutor,
@@ -1972,6 +1977,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mStatusBarState = mStatusBarStateController.getState();
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
mSecureSettings = secureSettings;
dumpManager.registerDumpable(getClass().getName(), this);
mSensorPrivacyManager = sensorPrivacyManager;
mActiveUnlockConfig = activeUnlockConfiguration;
@@ -2214,9 +2220,37 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Settings.System.TIME_12_24)));
}
};
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.TIME_12_24),
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
if (isSfpsSupported() && isSfpsEnrolled()) {
updateSfpsRequireScreenOnToAuthPref();
mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
updateSfpsRequireScreenOnToAuthPref();
}
};
mContext.getContentResolver().registerContentObserver(
mSecureSettings.getUriFor(
Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED),
false,
mSfpsRequireScreenOnToAuthPrefObserver,
getCurrentUser());
}
}
protected void updateSfpsRequireScreenOnToAuthPref() {
final int defaultSfpsRequireScreenOnToAuthValue =
mContext.getResources().getBoolean(
com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0;
mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser(
Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
defaultSfpsRequireScreenOnToAuthValue,
getCurrentUser()) != 0;
}
private void initializeSimState() {
@@ -2260,6 +2294,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
&& !mAuthController.getUdfpsProps().isEmpty();
}
/**
* @return true if there's at least one sfps enrollment for the current user.
*/
public boolean isSfpsEnrolled() {
return mAuthController.isSfpsEnrolled(getCurrentUser());
}
/**
* @return true if sfps HW is supported on this device. Can return true even if the user has
* not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
*/
public boolean isSfpsSupported() {
return mAuthController.getSfpsProps() != null
&& !mAuthController.getSfpsProps().isEmpty();
}
/**
* @return true if there's at least one face enrolled
*/
@@ -2590,8 +2640,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
&& !isEncryptedOrLockdownForUser
&& userDoesNotHaveTrust);
final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
&& shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
boolean shouldListenSfpsState = true;
// If mSfpsRequireScreenOnToAuthPrefEnabled, require screen on to listen to SFPS
if (isSfpsSupported() && isSfpsEnrolled() && mSfpsRequireScreenOnToAuthPrefEnabled) {
shouldListenSfpsState = isDeviceInteractive();
}
boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
&& shouldListenBouncerState && !isFingerprintLockedOut();
if (isUdfpsSupported()) {
shouldListen = shouldListen && shouldListenUdfpsState;
}
if (isSfpsSupported()) {
shouldListen = shouldListen && shouldListenSfpsState;
}
maybeLogListenerModelData(
new KeyguardFingerprintListenModel(
@@ -2613,6 +2677,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mKeyguardOccluded,
mOccludingAppRequestingFp,
mIsPrimaryUser,
shouldListenSfpsState,
shouldListenForFingerprintAssistant,
mSwitchingUser,
isUdfps,
@@ -3714,6 +3779,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);
}
if (mSfpsRequireScreenOnToAuthPrefObserver != null) {
mContext.getContentResolver().unregisterContentObserver(
mSfpsRequireScreenOnToAuthPrefObserver);
}
try {
ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
} catch (RemoteException e) {
@@ -3786,6 +3856,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
pw.println(" mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing);
pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState));
pw.println(" mUdfpsBouncerShowing=" + mUdfpsBouncerShowing);
} else if (isSfpsSupported()) {
pw.println(" sfpsEnrolled=" + isSfpsEnrolled());
pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false));
if (isSfpsEnrolled()) {
pw.println(" mSfpsRequireScreenOnToAuthPrefEnabled="
+ mSfpsRequireScreenOnToAuthPrefEnabled);
}
}
}
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {

View File

@@ -160,6 +160,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
@NonNull private final SparseBooleanArray mFaceEnrolledForUser;
@NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
@NonNull private final SensorPrivacyManager mSensorPrivacyManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private boolean mAllFingerprintAuthenticatorsRegistered;
@@ -365,6 +366,15 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
}
}
}
if (mSidefpsProps == null) {
Log.d(TAG, "handleEnrollmentsChanged, mSidefpsProps is null");
} else {
for (FingerprintSensorPropertiesInternal prop : mSidefpsProps) {
if (prop.sensorId == sensorId) {
mSfpsEnrolledForUser.put(userId, hasEnrollments);
}
}
}
for (Callback cb : mCallbacks) {
cb.onEnrollmentsChanged(modality);
}
@@ -722,6 +732,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mWindowManager = windowManager;
mInteractionJankMonitor = jankMonitor;
mUdfpsEnrolledForUser = new SparseBooleanArray();
mSfpsEnrolledForUser = new SparseBooleanArray();
mFaceEnrolledForUser = new SparseBooleanArray();
mVibratorHelper = vibrator;
@@ -964,6 +975,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
return mUdfpsProps;
}
@Nullable
public List<FingerprintSensorPropertiesInternal> getSfpsProps() {
return mSidefpsProps;
}
private String getErrorString(@Modality int modality, int error, int vendorCode) {
switch (modality) {
case TYPE_FACE:
@@ -1090,6 +1106,17 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
return mUdfpsEnrolledForUser.get(userId);
}
/**
* Whether the passed userId has enrolled SFPS.
*/
public boolean isSfpsEnrolled(int userId) {
if (mSidefpsController == null) {
return false;
}
return mSfpsEnrolledForUser.get(userId);
}
/** If BiometricPrompt is currently being shown to the user. */
public boolean isShowing() {
return mCurrentDialog != null;

View File

@@ -72,6 +72,7 @@ private fun fingerprintModel(user: Int) = KeyguardFingerprintListenModel(
keyguardOccluded = false,
occludingAppRequestingFp = false,
primaryUser = false,
shouldListenSfpsState = false,
shouldListenForFingerprintAssistant = false,
switchingUser = false,
udfps = false,

View File

@@ -21,6 +21,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -39,6 +40,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -54,6 +56,7 @@ import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -61,18 +64,21 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
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.biometrics.SensorProperties;
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.net.Uri;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -111,6 +117,7 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Assert;
@@ -182,6 +189,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
private SecureSettings mSecureSettings;
@Mock
private TelephonyManager mTelephonyManager;
@Mock
private SensorPrivacyManager mSensorPrivacyManager;
@@ -215,6 +224,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
private GlobalSettings mGlobalSettings;
private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
private final int mCurrentUserId = 100;
private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -224,6 +234,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
@Mock
private Uri mURI;
// Direct executor
private final Executor mBackgroundExecutor = Runnable::run;
private final Executor mMainExecutor = Runnable::run;
@@ -305,6 +318,15 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
final ContentResolver contentResolver = mContext.getContentResolver();
ExtendedMockito.spyOn(contentResolver);
doNothing().when(contentResolver)
.registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class),
anyInt());
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
verify(mBiometricManager)
@@ -1136,6 +1158,67 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
}
@Test
public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled()
throws RemoteException {
// SFPS supported and enrolled
setup_SfpsProps();
// WHEN require screen on to auth is disabled, and keyguard is not awake
when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0);
mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true);
// Preconditions for sfps auth to run
keyguardNotGoingAway();
currentUserIsPrimary();
currentUserDoesNotHaveTrust();
biometricsNotDisabledThroughDevicePolicyManager();
biometricsEnabledForCurrentUser();
userNotCurrentlySwitching();
statusBarShadeIsLocked();
mTestableLooper.processAllMessages();
// THEN we should listen for sfps when screen off, because require screen on is disabled
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
// WHEN require screen on to auth is enabled, and keyguard is not awake
when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1);
mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
// THEN we shouldn't listen for sfps when screen off, because require screen on is enabled
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
// Device now awake & keyguard is now interactive
deviceNotGoingToSleep();
deviceIsInteractive();
keyguardIsVisible();
// THEN we should listen for sfps when screen on, and require screen on is enabled
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
}
private void setup_SfpsProps() {
final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
when(mAuthController.getSfpsProps()).thenReturn(props);
when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
}
private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal(
@FingerprintSensorProperties.SensorType int sensorType) {
return new FingerprintSensorPropertiesInternal(
0 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
1 /* maxEnrollmentsPerUser */,
new ArrayList<ComponentInfoInternal>(),
sensorType,
true /* resetLockoutRequiresHardwareAuthToken */);
}
@Test
public void testShouldNotListenForUdfps_whenTrustEnabled() {
// GIVEN a "we should listen for udfps" state
@@ -1805,7 +1888,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
protected TestableKeyguardUpdateMonitor(Context context) {
super(context,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
mBroadcastDispatcher, mDumpManager,
mBroadcastDispatcher, mSecureSettings, mDumpManager,
mBackgroundExecutor, mMainExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager,