/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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.PowerManager import android.os.PowerManager.WAKE_REASON_BIOMETRIC 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.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.settings.FakeSettings import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.io.PrintWriter @SmallTest class ActiveUnlockConfigTest : SysuiTestCase() { private lateinit var secureSettings: FakeSettings @Mock private lateinit var contentResolver: ContentResolver @Mock private lateinit var handler: Handler @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var mockPrintWriter: PrintWriter @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor private lateinit var activeUnlockConfig: ActiveUnlockConfig private var currentUser: Int = 0 @Before fun setUp() { MockitoAnnotations.initMocks(this) currentUser = KeyguardUpdateMonitor.getCurrentUser() secureSettings = FakeSettings() activeUnlockConfig = ActiveUnlockConfig( handler, secureSettings, contentResolver, dumpManager ) } @Test fun registersForSettingsChanges() { verifyRegisterSettingObserver() } @Test fun onWakeupSettingChanged() { // GIVEN no active unlock settings enabled assertFalse( activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE) ) // WHEN unlock on wake is allowed secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_WAKE, 1, currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE)) // THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure assertTrue( activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE) ) assertTrue( activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT) ) assertTrue( activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL) ) } @Test fun onUnlockIntentSettingChanged() { // GIVEN no active unlock settings enabled assertFalse( activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT) ) // WHEN unlock on biometric failed is allowed secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 1, currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT)) // THEN active unlock triggers allowed on: biometric failure ONLY assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)) assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)) assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL)) } @Test fun onBioFailSettingChanged() { // 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) secureSettings.putStringForUser( ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "", currentUser) updateSetting(secureSettings.getUriFor( ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED )) assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL)) // WHEN unlock on biometric failed is allowed secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // THEN active unlock triggers allowed on: biometric failure ONLY assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)) assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)) assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL)) } @Test fun faceErrorSettingsChanged() { // GIVEN unlock on biometric fail secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // WHEN face error timeout (3), allow trigger active unlock secureSettings.putStringForUser( ACTIVE_UNLOCK_ON_FACE_ERRORS, "3", currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS)) // THEN active unlock triggers allowed on error TIMEOUT assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError( BiometricFaceConstants.FACE_ERROR_TIMEOUT)) assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError( BiometricFaceConstants.FACE_ERROR_CANCELED)) } @Test fun faceAcquiredSettingsChanged() { // GIVEN unlock on biometric fail secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, "1", currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // WHEN face acquiredMsg DARK_GLASSESand MOUTH_COVERING are allowed to trigger secureSettings.putStringForUser( ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" + "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}", currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)) // 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 triggerOnUnlockIntentWhenBiometricEnrolledNone() { // GIVEN unlock on biometric fail secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // 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) secureSettings.putStringForUser( ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "${ActiveUnlockConfig.BiometricType.NONE.intValue}", currentUser) updateSetting(secureSettings.getUriFor( ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED )) // THEN active unlock triggers allowed on unlock intent assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)) } @Test fun triggerOnUnlockIntentWhenBiometricEnrolledFingerprintOrFaceOnly() { // GIVEN unlock on biometric fail secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // 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 secureSettings.putStringForUser( ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" + "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}", currentUser) updateSetting(secureSettings.getUriFor( ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED )) // THEN active unlock triggers NOT allowed on unlock intent assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.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.ActiveUnlockRequestOrigin.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.ActiveUnlockRequestOrigin.UNLOCK_INTENT)) } @Test fun isWakeupConsideredUnlockIntent_singleValue() { // GIVEN lift is considered an unlock intent secureSettings.putIntForUser( ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, PowerManager.WAKE_REASON_LIFT, currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)) // THEN only WAKE_REASON_LIFT is considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { if (wakeReason == PowerManager.WAKE_REASON_LIFT) { assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason)) } else { assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason)) } } } @Test fun isWakeupConsideredUnlockIntent_multiValue() { // GIVEN lift and tap are considered an unlock intent secureSettings.putStringForUser( ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, PowerManager.WAKE_REASON_LIFT.toString() + "|" + PowerManager.WAKE_REASON_TAP.toString(), currentUser ) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)) // THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { if (wakeReason == PowerManager.WAKE_REASON_LIFT || wakeReason == PowerManager.WAKE_REASON_TAP) { assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason)) } else { assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason)) } } assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_LIFT)) assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP)) assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent( PowerManager.WAKE_REASON_UNFOLD_DEVICE)) } @Test fun isWakeupConsideredUnlockIntent_emptyValues() { // GIVEN lift and tap are considered an unlock intent secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, " ", currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)) // THEN no wake up gestures are considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason)) } assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent( PowerManager.WAKE_REASON_LIFT)) assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP)) assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent( PowerManager.WAKE_REASON_UNFOLD_DEVICE)) } @Test fun isWakeupForceDismissKeyguard_singleValue() { verifyRegisterSettingObserver() // GIVEN lift is considered an unlock intent secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD, PowerManager.WAKE_REASON_LIFT.toString(), currentUser) updateSetting(secureSettings.getUriFor( ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD )) // THEN only WAKE_REASON_LIFT is considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { if (wakeReason == PowerManager.WAKE_REASON_LIFT) { assertTrue(activeUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason)) } else { assertFalse(activeUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason)) } } } @Test fun isWakeupForceDismissKeyguard_emptyValues() { verifyRegisterSettingObserver() // GIVEN lift and tap are considered an unlock intent secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD, " ", currentUser) updateSetting(secureSettings.getUriFor( ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD )) // THEN no wake up gestures are considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { assertFalse(activeUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason)) } } @Test fun isWakeupForceDismissKeyguard_multiValue() { verifyRegisterSettingObserver() // GIVEN lift and tap are considered an unlock intent secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD, PowerManager.WAKE_REASON_LIFT.toString() + "|" + PowerManager.WAKE_REASON_TAP.toString(), currentUser ) updateSetting(secureSettings.getUriFor( ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD )) // THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { if (wakeReason == PowerManager.WAKE_REASON_LIFT || wakeReason == PowerManager.WAKE_REASON_TAP) { assertTrue(activeUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason)) } else { assertFalse(activeUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason)) } } } @Test fun dump_onUnlockIntentWhenBiometricEnrolled_invalidNum_noArrayOutOfBoundsException() { // GIVEN an invalid input (-1) secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "-1", currentUser) // WHEN the setting updates updateSetting(secureSettings.getUriFor( ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED )) // THEN no exception thrown activeUnlockConfig.dump(mockPrintWriter, emptyArray()) } private fun updateSetting(uri: Uri) { verifyRegisterSettingObserver() settingsObserverCaptor.value.onChange( false, listOf(uri), 0, 0 /* flags */ ) } private fun verifyRegisterSettingObserver() { verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE)) verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT)) verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS)) verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)) verifyRegisterSettingObserver(secureSettings.getUriFor( ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED )) verifyRegisterSettingObserver(secureSettings.getUriFor( ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS )) } private fun verifyRegisterSettingObserver(uri: Uri) { verify(contentResolver).registerContentObserver( eq(uri), eq(false), capture(settingsObserverCaptor), eq(UserHandle.USER_ALL)) } }