From ba0f3d6da2e622b540c25b208f02cab747053aba Mon Sep 17 00:00:00 2001 From: Beverly Date: Wed, 4 May 2022 21:26:16 +0000 Subject: [PATCH] Add additional toggleable active unlock settings Test: manual Test: atest ActiveUnlockConfigTest Fixes: 231391317 Fixes: 231390692 Fixes: 231390433 Change-Id: Icaf4c1e568e84ecf4d571537f30f7f9b0efb3e0c --- core/java/android/provider/Settings.java | 34 +++ .../settings/backup/SecureSettings.java | 3 + .../validators/SecureSettingsValidators.java | 5 + .../android/keyguard/ActiveUnlockConfig.kt | 197 +++++++++++++++--- .../keyguard/KeyguardUpdateMonitor.java | 29 ++- .../keyguard/ActiveUnlockConfigTest.kt | 193 ++++++++++++++--- 6 files changed, 396 insertions(+), 65 deletions(-) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index dac54cf6146e1..f35a458915452 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9694,6 +9694,40 @@ public final class Settings { public static final String ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL = "active_unlock_on_biometric_fail"; + /** + * If active unlock triggers on biometric failures, include the following error codes + * as a biometric failure. See {@link android.hardware.biometrics.BiometricFaceConstants}. + * Error codes should be separated by a pipe. For example: "1|4|5". If active unlock + * should never trigger on any face errors, this should be set to an empty string. + * A null value will use the system default value (TIMEOUT). + * @hide + */ + public static final String ACTIVE_UNLOCK_ON_FACE_ERRORS = + "active_unlock_on_face_errors"; + + /** + * If active unlock triggers on biometric failures, include the following acquired info + * as a "biometric failure". See {@link android.hardware.biometrics.BiometricFaceConstants}. + * Acquired codes should be separated by a pipe. For example: "1|4|5". If active unlock + * should never on trigger on any acquired info messages, this should be + * set to an empty string. A null value will use the system default value (none). + * @hide + */ + public static final String ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO = + "active_unlock_on_face_acquire_info"; + + /** + * If active unlock triggers on biometric failures, then also request active unlock on + * unlock intent when each setting (BiometricType) is the only biometric type enrolled. + * Biometric types should be separated by a pipe. For example: "0|3" or "0". If this + * setting should be disabled, then this should be set to an empty string. A null value + * will use the system default value (0 / None). + * 0 = None, 1 = Any face, 2 = Any fingerprint, 3 = Under display fingerprint + * @hide + */ + public static final String ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED = + "active_unlock_on_unlock_intent_when_biometric_enrolled"; + /** * Whether the assist gesture should be enabled. * diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 3029781f3e996..5eaf553a20479 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -120,6 +120,9 @@ public class SecureSettings { Settings.Secure.ACTIVE_UNLOCK_ON_WAKE, Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, + Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS, + Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, + Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, Settings.Secure.VR_DISPLAY_MODE, Settings.Secure.NOTIFICATION_BADGING, Settings.Secure.NOTIFICATION_DISMISS_RTL, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index a4da49713f87f..9ee7b654046f3 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -18,6 +18,7 @@ package android.provider.settings.validators; import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_COMPONENT_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_PACKAGE_LIST_VALIDATOR; @@ -176,6 +177,10 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_WAKE, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS, ANY_STRING_VALIDATOR); + VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, ANY_STRING_VALIDATOR); + VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + ANY_STRING_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR); diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt index f195d2094d6ab..38fa354534188 100644 --- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt +++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt @@ -16,14 +16,20 @@ package com.android.keyguard +import android.annotation.IntDef import android.content.ContentResolver import android.database.ContentObserver +import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT import android.net.Uri import android.os.Handler import android.os.UserHandle import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE +import android.util.Log import com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton @@ -44,6 +50,20 @@ class ActiveUnlockConfig @Inject constructor( dumpManager: DumpManager ) : Dumpable { + companion object { + const val TAG = "ActiveUnlockConfig" + + const val BIOMETRIC_TYPE_NONE = 0 + const val BIOMETRIC_TYPE_ANY_FACE = 1 + const val BIOMETRIC_TYPE_ANY_FINGERPRINT = 2 + const val BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT = 3 + } + + @Retention(AnnotationRetention.SOURCE) + @IntDef(BIOMETRIC_TYPE_NONE, BIOMETRIC_TYPE_ANY_FACE, BIOMETRIC_TYPE_ANY_FINGERPRINT, + BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT) + annotation class BiometricType + /** * Indicates the origin for an active unlock request. */ @@ -51,35 +71,50 @@ class ActiveUnlockConfig @Inject constructor( WAKE, UNLOCK_INTENT, BIOMETRIC_FAIL, ASSISTANT } + var keyguardUpdateMonitor: KeyguardUpdateMonitor? = null private var requestActiveUnlockOnWakeup = false private var requestActiveUnlockOnUnlockIntent = false private var requestActiveUnlockOnBioFail = false + private var faceErrorsToTriggerBiometricFailOn = mutableSetOf(FACE_ERROR_TIMEOUT) + private var faceAcquireInfoToTriggerBiometricFailOn = mutableSetOf() + private var onUnlockIntentWhenBiometricEnrolled = mutableSetOf(BIOMETRIC_TYPE_NONE) + private val settingsObserver = object : ContentObserver(handler) { - private val wakeUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE) - private val unlockIntentUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT) - private val bioFailUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL) + private val wakeUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE) + private val unlockIntentUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT) + private val bioFailUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL) + private val faceErrorsUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS) + private val faceAcquireInfoUri = + secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO) + private val unlockIntentWhenBiometricEnrolledUri = + secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED) fun register() { - contentResolver.registerContentObserver( - wakeUri, - false, - this, - UserHandle.USER_ALL) - contentResolver.registerContentObserver( - unlockIntentUri, - false, - this, - UserHandle.USER_ALL) - contentResolver.registerContentObserver( - bioFailUri, - false, - this, - UserHandle.USER_ALL) + registerUri( + listOf( + wakeUri, + unlockIntentUri, + bioFailUri, + faceErrorsUri, + faceAcquireInfoUri, + unlockIntentWhenBiometricEnrolledUri + ) + ) onChange(true, ArrayList(), 0, getCurrentUser()) } + private fun registerUri(uris: Collection) { + for (uri in uris) { + contentResolver.registerContentObserver( + uri, + false, + this, + UserHandle.USER_ALL) + } + } + override fun onChange( selfChange: Boolean, uris: Collection, @@ -104,6 +139,55 @@ class ActiveUnlockConfig @Inject constructor( requestActiveUnlockOnBioFail = secureSettings.getIntForUser( ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, getCurrentUser()) == 1 } + + if (selfChange || uris.contains(faceErrorsUri)) { + processStringArray( + secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS, + getCurrentUser()), + faceErrorsToTriggerBiometricFailOn, + setOf(FACE_ERROR_TIMEOUT)) + } + + if (selfChange || uris.contains(faceAcquireInfoUri)) { + processStringArray( + secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, + getCurrentUser()), + faceAcquireInfoToTriggerBiometricFailOn, + setOf()) + } + + if (selfChange || uris.contains(unlockIntentWhenBiometricEnrolledUri)) { + processStringArray( + secureSettings.getStringForUser( + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + getCurrentUser()), + onUnlockIntentWhenBiometricEnrolled, + setOf(BIOMETRIC_TYPE_NONE)) + } + } + + /** + * Convert a pipe-separated set of integers into a set of ints. + * @param stringSetting expected input are integers delineated by a pipe. For example, + * it may look something like this: "1|5|3". + * @param out updates the "out" Set will the integers between the pipes. + * @param default If stringSetting is null, "out" will be populated with values in "default" + */ + private fun processStringArray( + stringSetting: String?, + out: MutableSet, + default: Set + ) { + out.clear() + stringSetting?.let { + for (code: String in stringSetting.split("|")) { + try { + out.add(code.toInt()) + } catch (e: NumberFormatException) { + Log.e(TAG, "Passed an invalid setting=$code") + } + } + } ?: out.addAll(default) } } @@ -112,6 +196,30 @@ class ActiveUnlockConfig @Inject constructor( dumpManager.registerDumpable(this) } + /** + * If any active unlock triggers are enabled. + */ + fun isActiveUnlockEnabled(): Boolean { + return requestActiveUnlockOnWakeup || requestActiveUnlockOnUnlockIntent || + requestActiveUnlockOnBioFail + } + + /** + * Whether the face error code from {@link BiometricFaceConstants} should trigger + * active unlock on biometric failure. + */ + fun shouldRequestActiveUnlockOnFaceError(errorCode: Int): Boolean { + return faceErrorsToTriggerBiometricFailOn.contains(errorCode) + } + + /** + * Whether the face acquireInfo from {@link BiometricFaceConstants} should trigger + * active unlock on biometric failure. + */ + fun shouldRequestActiveUnlockOnFaceAcquireInfo(acquiredInfo: Int): Boolean { + return faceAcquireInfoToTriggerBiometricFailOn.contains(acquiredInfo) + } + /** * Whether to trigger active unlock based on where the request is coming from and * the current settings. @@ -121,7 +229,8 @@ class ActiveUnlockConfig @Inject constructor( ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE -> requestActiveUnlockOnWakeup ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT -> - requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup + requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup || + (shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()) ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL -> requestActiveUnlockOnBioFail || requestActiveUnlockOnUnlockIntent || @@ -131,17 +240,55 @@ class ActiveUnlockConfig @Inject constructor( } } - /** - * If any active unlock triggers are enabled. - */ - fun isActiveUnlockEnabled(): Boolean { - return requestActiveUnlockOnWakeup || requestActiveUnlockOnUnlockIntent || - requestActiveUnlockOnBioFail + private fun shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment(): Boolean { + if (!requestActiveUnlockOnBioFail) { + return false + } + + keyguardUpdateMonitor?.let { + val anyFaceEnrolled = it.isFaceEnrolled + val anyFingerprintEnrolled = + it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser()) + val udfpsEnrolled = it.isUdfpsEnrolled + + if (!anyFaceEnrolled && !anyFingerprintEnrolled) { + return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_NONE) + } + + if (!anyFaceEnrolled && anyFingerprintEnrolled) { + return onUnlockIntentWhenBiometricEnrolled.contains( + BIOMETRIC_TYPE_ANY_FINGERPRINT) || + (udfpsEnrolled && onUnlockIntentWhenBiometricEnrolled.contains( + BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT)) + } + + if (!anyFingerprintEnrolled && anyFaceEnrolled) { + return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_ANY_FACE) + } + } + + return false } override fun dump(pw: PrintWriter, args: Array) { + pw.println("Settings:") pw.println(" requestActiveUnlockOnWakeup=$requestActiveUnlockOnWakeup") pw.println(" requestActiveUnlockOnUnlockIntent=$requestActiveUnlockOnUnlockIntent") pw.println(" requestActiveUnlockOnBioFail=$requestActiveUnlockOnBioFail") + pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=" + + "$onUnlockIntentWhenBiometricEnrolled") + pw.println(" requestActiveUnlockOnFaceError=$faceErrorsToTriggerBiometricFailOn") + pw.println(" requestActiveUnlockOnFaceAcquireInfo=" + + "$faceAcquireInfoToTriggerBiometricFailOn") + + pw.println("Current state:") + keyguardUpdateMonitor?.let { + pw.println(" shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment=" + + "${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}") + pw.println(" faceEnrolled=${it.isFaceEnrolled}") + pw.println(" fpEnrolled=${ + it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())}") + pw.println(" udfpsEnrolled=${it.isUdfpsEnrolled}") + } ?: pw.println(" keyguardUpdateMonitor is uninitialized") } } \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index bbe9a362b1fac..121ac299ec5b0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -56,7 +56,6 @@ import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.hardware.SensorPrivacyManager; -import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; @@ -1615,7 +1614,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mKeyguardBypassController.setUserHasDeviceEntryIntent(false); } - if (errMsgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) { + if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(errMsgId)) { requestActiveUnlock( ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL, "faceError-" + errMsgId); @@ -1625,6 +1624,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationAcquired(int acquireInfo) { handleFaceAcquired(acquireInfo); + + if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + acquireInfo)) { + requestActiveUnlock( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL, + "faceAcquireInfo-" + acquireInfo); + } } }; @@ -1639,6 +1645,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mFingerprintLockedOut; private boolean mFingerprintLockedOutPermanent; private boolean mFaceLockedOutPermanent; + private HashMap mIsUnlockWithFingerprintPossible = new HashMap<>(); private TelephonyManager mTelephonyManager; /** @@ -1889,6 +1896,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab dumpManager.registerDumpable(getClass().getName(), this); mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); mActiveUnlockConfig = activeUnlockConfiguration; + mActiveUnlockConfig.setKeyguardUpdateMonitor(this); mHandler = new Handler(mainLooper) { @Override @@ -2329,7 +2337,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } if (shouldTriggerActiveUnlock()) { - if (DEBUG) { + if (DEBUG_ACTIVE_UNLOCK) { Log.d("ActiveUnlock", "initiate active unlock triggerReason=" + reason); } mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser()); @@ -2359,7 +2367,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } if (allowRequest && shouldTriggerActiveUnlock()) { - if (DEBUG) { + if (DEBUG_ACTIVE_UNLOCK) { Log.d("ActiveUnlock", "reportUserRequestedUnlock" + " origin=" + requestOrigin.name() + " reason=" + reason @@ -2777,8 +2785,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private boolean isUnlockWithFingerprintPossible(int userId) { - return mFpm != null && mFpm.isHardwareDetected() && !isFingerprintDisabled(userId) - && mFpm.hasEnrolledTemplates(userId); + mIsUnlockWithFingerprintPossible.put(userId, mFpm != null && mFpm.isHardwareDetected() + && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId)); + return mIsUnlockWithFingerprintPossible.get(userId); + } + + /** + * Cached value for whether fingerprint is enrolled and possible to use for authentication. + * Note: checking fingerprint enrollment directly with the AuthController requires an IPC. + */ + public boolean getCachedIsUnlockWithFingerprintPossible(int userId) { + return mIsUnlockWithFingerprintPossible.get(userId); } private boolean isUnlockWithFacePossible(int userId) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt index 747649006b455..39cc34bb7e265 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt @@ -18,6 +18,7 @@ package com.android.keyguard import android.content.ContentResolver import android.database.ContentObserver +import android.hardware.biometrics.BiometricFaceConstants import android.net.Uri import android.os.Handler import android.os.UserHandle @@ -44,18 +45,20 @@ class ActiveUnlockConfigTest : SysuiTestCase() { private val fakeWakeUri = Uri.Builder().appendPath("wake").build() private val fakeUnlockIntentUri = Uri.Builder().appendPath("unlock-intent").build() private val fakeBioFailUri = Uri.Builder().appendPath("bio-fail").build() + private val fakeFaceErrorsUri = Uri.Builder().appendPath("face-errors").build() + private val fakeFaceAcquiredUri = Uri.Builder().appendPath("face-acquired").build() + private val fakeUnlockIntentBioEnroll = Uri.Builder().appendPath("unlock-intent-bio").build() @Mock private lateinit var secureSettings: SecureSettings - @Mock private lateinit var contentResolver: ContentResolver - @Mock private lateinit var handler: Handler - @Mock private lateinit var dumpManager: DumpManager + @Mock + private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor @@ -72,6 +75,13 @@ class ActiveUnlockConfigTest : SysuiTestCase() { .thenReturn(fakeUnlockIntentUri) `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) .thenReturn(fakeBioFailUri) + `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS)) + .thenReturn(fakeFaceErrorsUri) + `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)) + .thenReturn(fakeFaceAcquiredUri) + `when`(secureSettings.getUriFor( + Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)) + .thenReturn(fakeUnlockIntentBioEnroll) activeUnlockConfig = ActiveUnlockConfig( handler, @@ -99,12 +109,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // WHEN unlock on wake is allowed `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_WAKE, 0, 0)).thenReturn(1) - settingsObserverCaptor.value.onChange( - false, - listOf(fakeWakeUri), - 0, - 0 - ) + updateSetting(fakeWakeUri) // THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure assertTrue( @@ -134,12 +139,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // WHEN unlock on biometric failed is allowed `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 0, 0)).thenReturn(1) - settingsObserverCaptor.value.onChange( - false, - listOf(fakeUnlockIntentUri), - 0, - 0 - ) + updateSetting(fakeUnlockIntentUri) // THEN active unlock triggers allowed on: biometric failure ONLY assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -154,19 +154,19 @@ class ActiveUnlockConfigTest : SysuiTestCase() { fun testOnBioFailSettingChanged() { verifyRegisterSettingObserver() - // GIVEN no active unlock settings enabled + // GIVEN no active unlock settings enabled and triggering unlock intent on biometric + // enrollment setting is disabled (empty string is disabled, null would use the default) + `when`(secureSettings.getStringForUser( + Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + 0)).thenReturn("") + updateSetting(fakeUnlockIntentBioEnroll) assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL)) // WHEN unlock on biometric failed is allowed `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, 0)).thenReturn(1) - settingsObserverCaptor.value.onChange( - false, - listOf(fakeBioFailUri), - 0, - 0 - ) + updateSetting(fakeBioFailUri) // THEN active unlock triggers allowed on: biometric failure ONLY assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -177,21 +177,146 @@ class ActiveUnlockConfigTest : SysuiTestCase() { ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL)) } + @Test + fun testFaceErrorSettingsChanged() { + verifyRegisterSettingObserver() + + // GIVEN unlock on biometric fail + `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, + 0, 0)).thenReturn(1) + updateSetting(fakeBioFailUri) + + // WHEN face error timeout (3), allow trigger active unlock + `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS, + 0)).thenReturn("3") + updateSetting(fakeFaceAcquiredUri) + + // THEN active unlock triggers allowed on error TIMEOUT + assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError( + BiometricFaceConstants.FACE_ERROR_TIMEOUT)) + + assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError( + BiometricFaceConstants.FACE_ERROR_CANCELED)) + } + + @Test + fun testFaceAcquiredSettingsChanged() { + verifyRegisterSettingObserver() + + // GIVEN unlock on biometric fail + `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, + 0, 0)).thenReturn(1) + updateSetting(fakeBioFailUri) + + // WHEN face acquiredMsg DARK_GLASSESand MOUTH_COVERING are allowed to trigger + `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, + 0)).thenReturn( + "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" + + "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}") + updateSetting(fakeFaceAcquiredUri) + + // THEN active unlock triggers allowed on acquired messages DARK_GLASSES & MOUTH_COVERING + assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED)) + assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED)) + + assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + BiometricFaceConstants.FACE_ACQUIRED_GOOD)) + assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED)) + } + + @Test + fun testTriggerOnUnlockIntentWhenBiometricEnrolledNone() { + verifyRegisterSettingObserver() + + // GIVEN unlock on biometric fail + `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, + 0, 0)).thenReturn(1) + updateSetting(fakeBioFailUri) + + // GIVEN fingerprint and face are NOT enrolled + activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor + `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false) + `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false) + + // WHEN unlock intent is allowed when NO biometrics are enrolled (0) + `when`(secureSettings.getStringForUser( + Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + 0)).thenReturn("${ActiveUnlockConfig.BIOMETRIC_TYPE_NONE}") + updateSetting(fakeUnlockIntentBioEnroll) + + // THEN active unlock triggers allowed on unlock intent + assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)) + } + + @Test + fun testTriggerOnUnlockIntentWhenBiometricEnrolledFingerprintOrFaceOnly() { + verifyRegisterSettingObserver() + + // GIVEN unlock on biometric fail + `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, + 0, 0)).thenReturn(1) + updateSetting(fakeBioFailUri) + + // GIVEN fingerprint and face are both enrolled + activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor + `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true) + `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true) + + // WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs + // are enrolled + `when`(secureSettings.getStringForUser( + Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + 0)).thenReturn( + "${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FACE}" + + "|${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FINGERPRINT}") + updateSetting(fakeUnlockIntentBioEnroll) + + // THEN active unlock triggers NOT allowed on unlock intent + assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)) + + // WHEN fingerprint ONLY enrolled + `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false) + `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true) + + // THEN active unlock triggers allowed on unlock intent + assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)) + + // WHEN face ONLY enrolled + `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true) + `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false) + + // THEN active unlock triggers allowed on unlock intent + assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)) + } + + private fun updateSetting(uri: Uri) { + settingsObserverCaptor.value.onChange( + false, + listOf(uri), + 0, + 0 /* flags */ + ) + } + private fun verifyRegisterSettingObserver() { - verify(contentResolver).registerContentObserver( - eq(fakeWakeUri), - eq(false), - capture(settingsObserverCaptor), - eq(UserHandle.USER_ALL)) + verifyRegisterSettingObserver(fakeWakeUri) + verifyRegisterSettingObserver(fakeUnlockIntentUri) + verifyRegisterSettingObserver(fakeBioFailUri) + verifyRegisterSettingObserver(fakeFaceErrorsUri) + verifyRegisterSettingObserver(fakeFaceAcquiredUri) + verifyRegisterSettingObserver(fakeUnlockIntentBioEnroll) + } + private fun verifyRegisterSettingObserver(uri: Uri) { verify(contentResolver).registerContentObserver( - eq(fakeUnlockIntentUri), - eq(false), - capture(settingsObserverCaptor), - eq(UserHandle.USER_ALL)) - - verify(contentResolver).registerContentObserver( - eq(fakeBioFailUri), + eq(uri), eq(false), capture(settingsObserverCaptor), eq(UserHandle.USER_ALL))