diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt new file mode 100644 index 0000000000000..f195d2094d6ab --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt @@ -0,0 +1,147 @@ +/* + * 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.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_UNLOCK_INTENT +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE +import com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser +import com.android.systemui.Dumpable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager +import com.android.systemui.util.settings.SecureSettings +import java.io.PrintWriter +import javax.inject.Inject + +/** + * Handles active unlock settings changes. + */ +@SysUISingleton +class ActiveUnlockConfig @Inject constructor( + @Main private val handler: Handler, + private val secureSettings: SecureSettings, + private val contentResolver: ContentResolver, + dumpManager: DumpManager +) : Dumpable { + + /** + * Indicates the origin for an active unlock request. + */ + enum class ACTIVE_UNLOCK_REQUEST_ORIGIN { + WAKE, UNLOCK_INTENT, BIOMETRIC_FAIL, ASSISTANT + } + + private var requestActiveUnlockOnWakeup = false + private var requestActiveUnlockOnUnlockIntent = false + private var requestActiveUnlockOnBioFail = false + + 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) + + 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) + + onChange(true, ArrayList(), 0, getCurrentUser()) + } + + override fun onChange( + selfChange: Boolean, + uris: Collection, + flags: Int, + userId: Int + ) { + if (getCurrentUser() != userId) { + return + } + + if (selfChange || uris.contains(wakeUri)) { + requestActiveUnlockOnWakeup = secureSettings.getIntForUser( + ACTIVE_UNLOCK_ON_WAKE, 0, getCurrentUser()) == 1 + } + + if (selfChange || uris.contains(unlockIntentUri)) { + requestActiveUnlockOnUnlockIntent = secureSettings.getIntForUser( + ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 0, getCurrentUser()) == 1 + } + + if (selfChange || uris.contains(bioFailUri)) { + requestActiveUnlockOnBioFail = secureSettings.getIntForUser( + ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, getCurrentUser()) == 1 + } + } + } + + init { + settingsObserver.register() + dumpManager.registerDumpable(this) + } + + /** + * Whether to trigger active unlock based on where the request is coming from and + * the current settings. + */ + fun shouldAllowActiveUnlockFromOrigin(requestOrigin: ACTIVE_UNLOCK_REQUEST_ORIGIN): Boolean { + return when (requestOrigin) { + ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE -> requestActiveUnlockOnWakeup + + ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT -> + requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup + + ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL -> + requestActiveUnlockOnBioFail || requestActiveUnlockOnUnlockIntent || + requestActiveUnlockOnWakeup + + ACTIVE_UNLOCK_REQUEST_ORIGIN.ASSISTANT -> isActiveUnlockEnabled() + } + } + + /** + * If any active unlock triggers are enabled. + */ + fun isActiveUnlockEnabled(): Boolean { + return requestActiveUnlockOnWakeup || requestActiveUnlockOnUnlockIntent || + requestActiveUnlockOnBioFail + } + + override fun dump(pw: PrintWriter, args: Array) { + pw.println(" requestActiveUnlockOnWakeup=$requestActiveUnlockOnWakeup") + pw.println(" requestActiveUnlockOnUnlockIntent=$requestActiveUnlockOnUnlockIntent") + pw.println(" requestActiveUnlockOnBioFail=$requestActiveUnlockOnBioFail") + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 28a3dbbf6c834..965fcd2653f44 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -221,10 +221,10 @@ public class KeyguardSecurityContainerController extends ViewController + + private lateinit var activeUnlockConfig: ActiveUnlockConfig + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_WAKE)) + .thenReturn(fakeWakeUri) + `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT)) + .thenReturn(fakeUnlockIntentUri) + `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) + .thenReturn(fakeBioFailUri) + + activeUnlockConfig = ActiveUnlockConfig( + handler, + secureSettings, + contentResolver, + dumpManager + ) + } + + @Test + fun testRegsitersForSettingsChanges() { + verifyRegisterSettingObserver() + } + + @Test + fun testOnWakeupSettingChanged() { + verifyRegisterSettingObserver() + + // GIVEN no active unlock settings enabled + assertFalse( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE) + ) + + // 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 + ) + + // THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure + assertTrue( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE) + ) + assertTrue( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT) + ) + assertTrue( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL) + ) + } + + @Test + fun testOnUnlockIntentSettingChanged() { + verifyRegisterSettingObserver() + + // GIVEN no active unlock settings enabled + assertFalse( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT) + ) + + // 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 + ) + + // THEN active unlock triggers allowed on: biometric failure ONLY + assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE)) + assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)) + assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL)) + } + + @Test + fun testOnBioFailSettingChanged() { + verifyRegisterSettingObserver() + + // GIVEN no active unlock settings enabled + 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 + ) + + // THEN active unlock triggers allowed on: biometric failure ONLY + assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE)) + assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)) + assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL)) + } + + private fun verifyRegisterSettingObserver() { + verify(contentResolver).registerContentObserver( + eq(fakeWakeUri), + eq(false), + capture(settingsObserverCaptor), + eq(UserHandle.USER_ALL)) + + verify(contentResolver).registerContentObserver( + eq(fakeUnlockIntentUri), + eq(false), + capture(settingsObserverCaptor), + eq(UserHandle.USER_ALL)) + + verify(contentResolver).registerContentObserver( + eq(fakeBioFailUri), + eq(false), + capture(settingsObserverCaptor), + eq(UserHandle.USER_ALL)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 86a4f5ad43b03..2dc066c8a9dba 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -177,6 +177,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private ArgumentCaptor mStatusBarStateListenerCaptor; @Mock private KeyguardUpdateMonitorCallback mTestCallback; + @Mock + private ActiveUnlockConfig mActiveUnlockConfig; // Direct executor private Executor mBackgroundExecutor = Runnable::run; private Executor mMainExecutor = Runnable::run; @@ -1188,7 +1190,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mBackgroundExecutor, mMainExecutor, mStatusBarStateController, mLockPatternUtils, mAuthController, mTelephonyListenerManager, - mInteractionJankMonitor, mLatencyTracker); + mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 7779f42f1a2ea..da5939ae4a12c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -212,6 +212,7 @@ public class UdfpsControllerTest extends SysuiTestCase { .thenReturn(mFpmOtherView); when(mEnrollView.getContext()).thenReturn(mContext); when(mKeyguardStateController.isOccluded()).thenReturn(false); + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); final List props = new ArrayList<>(); final List componentInfo = new ArrayList<>();