diff options
author | 2024-11-22 21:07:57 +0000 | |
---|---|---|
committer | 2024-11-22 21:07:57 +0000 | |
commit | 43b90eb68c0db7b94fa26430af5d96152ff857b9 (patch) | |
tree | 73a0a5839a1cfb348f0dc8fe0044e47361a3321d | |
parent | 735f04b65b523ae47e66d760eba8a9745f410eb0 (diff) | |
parent | e5e3b5bcae68ef0ec8c0285f3d416827cfd8e313 (diff) |
Merge "Add keyguard suppression interactors." into main
12 files changed, 354 insertions, 135 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt index bd26e4205242..bef995fa5225 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.internal.widget.lockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.biometricSettingsRepository @@ -33,6 +34,8 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @@ -52,14 +55,21 @@ class KeyguardLockWhileAwakeInteractorTest : SysuiTestCase() { testScope.runTest { val values by collectValues(underTest.lockWhileAwakeEvents) - underTest.onKeyguardServiceDoKeyguardTimeout(options = null) + kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true) + runCurrent() + + kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout( + options = null + ) runCurrent() assertThat(values) .containsExactly(LockWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON) advanceTimeBy(1000) - underTest.onKeyguardServiceDoKeyguardTimeout(options = null) + kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout( + options = null + ) runCurrent() assertThat(values) @@ -69,8 +79,15 @@ class KeyguardLockWhileAwakeInteractorTest : SysuiTestCase() { ) } + /** + * We re-show keyguard when it's re-enabled, but only if it was originally showing when we + * disabled it. + * + * If it wasn't showing when originally disabled it, re-enabling it should do nothing (the + * keyguard will re-show next time we're locked). + */ @Test - fun emitsWhenKeyguardEnabled_onlyIfShowingWhenDisabled() = + fun emitsWhenKeyguardReenabled_onlyIfShowingWhenDisabled() = testScope.runTest { val values by collectValues(underTest.lockWhileAwakeEvents) @@ -98,4 +115,49 @@ class KeyguardLockWhileAwakeInteractorTest : SysuiTestCase() { assertThat(values).containsExactly(LockWhileAwakeReason.KEYGUARD_REENABLED) } + + /** + * Un-suppressing keyguard should never cause us to re-show. We'll re-show when we're next + * locked, even if we were showing when originally suppressed. + */ + @Test + fun doesNotEmit_keyguardNoLongerSuppressed() = + testScope.runTest { + val values by collectValues(underTest.lockWhileAwakeEvents) + + // Enable keyguard and then suppress it. + kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true) + whenever(kosmos.lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true) + runCurrent() + + assertEquals(0, values.size) + + // Un-suppress keyguard. + whenever(kosmos.lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false) + runCurrent() + + assertEquals(0, values.size) + } + + /** + * Lockdown and lockNow() should not cause us to lock while awake if we are suppressed via adb. + */ + @Test + fun doesNotEmit_fromLockdown_orFromLockNow_ifEnabledButSuppressed() = + testScope.runTest { + val values by collectValues(underTest.lockWhileAwakeEvents) + + // Set keyguard enabled, but then disable lockscreen (suppress it). + kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true) + whenever(kosmos.lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true) + runCurrent() + + kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(null) + runCurrent() + + kosmos.biometricSettingsRepository.setIsUserInLockdown(true) + runCurrent() + + assertEquals(0, values.size) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt index 7e249e8c179d..ead151ee6df2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt @@ -87,9 +87,9 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { assertEquals( listOf( - false, // Defaults to false. + false // Defaults to false. ), - canWake + canWake, ) repository.setKeyguardEnabled(false) @@ -100,33 +100,26 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { false, // Default to false. true, // True now that keyguard service is disabled ), - canWake + canWake, ) repository.setKeyguardEnabled(true) runCurrent() - assertEquals( - listOf( - false, - true, - false, - ), - canWake - ) + assertEquals(listOf(false, true, false), canWake) } @Test @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) - fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled() = + fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled_onlyAfterWakefulnessChange() = testScope.runTest { val canWake by collectValues(underTest.canWakeDirectlyToGone) assertEquals( listOf( - false, // Defaults to false. + false // Defaults to false. ), - canWake + canWake, ) whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true) @@ -136,9 +129,9 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { listOf( // Still false - isLockScreenDisabled only causes canWakeDirectlyToGone to // update on the next wake/sleep event. - false, + false ), - canWake + canWake, ) kosmos.powerInteractor.setAsleepForTest() @@ -150,7 +143,7 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { // True since we slept after setting isLockScreenDisabled=true true, ), - canWake + canWake, ) kosmos.powerInteractor.setAwakeForTest() @@ -159,25 +152,75 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { kosmos.powerInteractor.setAsleepForTest() runCurrent() + assertEquals(listOf(false, true), canWake) + + whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false) + kosmos.powerInteractor.setAwakeForTest() + runCurrent() + + assertEquals(listOf(false, true, false), canWake) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled_lockNowEvent() = + testScope.runTest { + val canWake by collectValues(underTest.canWakeDirectlyToGone) + + assertEquals( + listOf( + false // Defaults to false. + ), + canWake, + ) + + whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true) + runCurrent() + + assertEquals( + listOf( + // Still false - isLockScreenDisabled only causes canWakeDirectlyToGone to + // update on the next wakefulness or lockNow event. + false + ), + canWake, + ) + + kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(null) + runCurrent() + assertEquals( listOf( false, + // True when lockNow() called after setting isLockScreenDisabled=true true, ), - canWake + canWake, ) whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false) - kosmos.powerInteractor.setAwakeForTest() + runCurrent() + + assertEquals( + listOf( + false, + // Still true since no lockNow() calls made. + true, + ), + canWake, + ) + + kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(null) runCurrent() assertEquals( listOf( false, true, + // False again after the lockNow() call. false, ), - canWake + canWake, ) } @@ -189,9 +232,9 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { assertEquals( listOf( - false, // Defaults to false. + false // Defaults to false. ), - canWake + canWake, ) repository.setBiometricUnlockState(BiometricUnlockMode.WAKE_AND_UNLOCK) @@ -213,9 +256,9 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { assertEquals( listOf( - false, // Defaults to false. + false // Defaults to false. ), - canWake + canWake, ) repository.setCanIgnoreAuthAndReturnToGone(true) @@ -237,9 +280,9 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { assertEquals( listOf( - false, // Defaults to false. + false // Defaults to false. ), - canWake + canWake, ) whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt())) @@ -257,13 +300,7 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { ) runCurrent() - assertEquals( - listOf( - false, - true, - ), - canWake - ) + assertEquals(listOf(false, true), canWake) verify(kosmos.alarmManager) .setExactAndAllowWhileIdle( @@ -281,9 +318,9 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { assertEquals( listOf( - false, // Defaults to false. + false // Defaults to false. ), - canWake + canWake, ) whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt())) @@ -312,7 +349,7 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { // Timed out, so we can ignore auth/return to GONE. true, ), - canWake + canWake, ) verify(kosmos.alarmManager) @@ -338,7 +375,7 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { // alarm in flight that should be canceled. false, ), - canWake + canWake, ) kosmos.powerInteractor.setAsleepForTest( @@ -354,25 +391,17 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { // Back to sleep. true, ), - canWake + canWake, ) // Simulate the first sleep's alarm coming in. lastRegisteredBroadcastReceiver?.onReceive( kosmos.mockedContext, - Intent("com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD") + Intent("com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD"), ) runCurrent() // It should not have any effect. - assertEquals( - listOf( - false, - true, - false, - true, - ), - canWake - ) + assertEquals(listOf(false, true, false, true), canWake) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 7097c1d2fc4e..d40fe468b0a5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -81,7 +81,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; -import com.android.systemui.keyguard.domain.interactor.KeyguardLockWhileAwakeInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardServiceLockNowInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardStateCallbackInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardWakeDirectlyToGoneInteractor; import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier; @@ -330,8 +330,7 @@ public class KeyguardService extends Service { return new FoldGracePeriodProvider(); } }; - private final KeyguardLockWhileAwakeInteractor - mKeyguardLockWhileAwakeInteractor; + private final KeyguardServiceLockNowInteractor mKeyguardServiceLockNowInteractor; @Inject public KeyguardService( @@ -357,7 +356,7 @@ public class KeyguardService extends Service { KeyguardDismissInteractor keyguardDismissInteractor, Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy, KeyguardStateCallbackInteractor keyguardStateCallbackInteractor, - KeyguardLockWhileAwakeInteractor keyguardLockWhileAwakeInteractor) { + KeyguardServiceLockNowInteractor keyguardServiceLockNowInteractor) { super(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher; @@ -389,7 +388,7 @@ public class KeyguardService extends Service { mKeyguardEnabledInteractor = keyguardEnabledInteractor; mKeyguardWakeDirectlyToGoneInteractor = keyguardWakeDirectlyToGoneInteractor; mKeyguardDismissInteractor = keyguardDismissInteractor; - mKeyguardLockWhileAwakeInteractor = keyguardLockWhileAwakeInteractor; + mKeyguardServiceLockNowInteractor = keyguardServiceLockNowInteractor; } @Override @@ -665,7 +664,7 @@ public class KeyguardService extends Service { if (SceneContainerFlag.isEnabled()) { mDeviceEntryInteractorLazy.get().lockNow(); } else if (KeyguardWmStateRefactor.isEnabled()) { - mKeyguardLockWhileAwakeInteractor.onKeyguardServiceDoKeyguardTimeout(options); + mKeyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(options); } mKeyguardViewMediator.doKeyguardTimeout(options); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 01ec4d026646..9f131607cb99 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -3943,7 +3943,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } private void notifyDefaultDisplayCallbacks(boolean showing) { - if (SceneContainerFlag.isEnabled()) { + if (SceneContainerFlag.isEnabled() || KeyguardWmStateRefactor.isEnabled()) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt index 631e44aca26d..42cbd7d39248 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt @@ -16,39 +16,52 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.internal.widget.LockPatternUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.sample import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import com.android.app.tracing.coroutines.launchTraced as launch +import kotlinx.coroutines.withContext /** - * Logic around the keyguard being enabled/disabled, per [KeyguardService]. If the keyguard is not - * enabled, the lockscreen cannot be shown and the device will go from AOD/DOZING directly to GONE. + * Logic around the keyguard being enabled, disabled, or suppressed via adb. If the keyguard is + * disabled or suppressed, the lockscreen cannot be shown and the device will go from AOD/DOZING + * directly to GONE. * * Keyguard can be disabled by selecting Security: "None" in settings, or by apps that hold * permission to do so (such as Phone). Some CTS tests also disable keyguard in onCreate or onStart * rather than simply dismissing the keyguard or setting up the device to have Security: None, for * reasons unknown. + * + * Keyguard can be suppressed by calling "adb shell locksettings set-disabled true", which is + * frequently done in tests. If keyguard is suppressed, it won't show even if the keyguard is + * enabled. If keyguard is not suppressed, then we defer to whether keyguard is enabled or disabled. */ @SysUISingleton class KeyguardEnabledInteractor @Inject constructor( - @Application scope: CoroutineScope, + @Application val scope: CoroutineScope, + @Background val backgroundDispatcher: CoroutineDispatcher, val repository: KeyguardRepository, val biometricSettingsRepository: BiometricSettingsRepository, - keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor, + private val selectedUserInteractor: SelectedUserInteractor, + private val lockPatternUtils: LockPatternUtils, + keyguardDismissTransitionInteractor: dagger.Lazy<KeyguardDismissTransitionInteractor>, internalTransitionInteractor: InternalKeyguardTransitionInteractor, ) { @@ -62,6 +75,10 @@ constructor( * If the keyguard is disabled while we're locked, we will transition to GONE unless we're in * lockdown mode. If the keyguard is re-enabled, we'll transition back to LOCKSCREEN if we were * locked when it was disabled. + * + * Even if the keyguard is enabled, it's possible for it to be suppressed temporarily via adb. + * If you need to respect that adb command, you will need to use + * [isKeyguardEnabledAndNotSuppressed] instead of using this flow. */ val isKeyguardEnabled: StateFlow<Boolean> = repository.isKeyguardEnabled @@ -96,9 +113,9 @@ constructor( val currentTransitionInfo = internalTransitionInteractor.currentTransitionInfoInternal() if (currentTransitionInfo.to != KeyguardState.GONE && !inLockdown) { - keyguardDismissTransitionInteractor.startDismissKeyguardTransition( - "keyguard disabled" - ) + keyguardDismissTransitionInteractor + .get() + .startDismissKeyguardTransition("keyguard disabled") } } } @@ -116,4 +133,37 @@ constructor( fun isShowKeyguardWhenReenabled(): Boolean { return repository.isShowKeyguardWhenReenabled() } + + /** + * Whether the keyguard is enabled, and has not been suppressed via adb. + * + * There is unfortunately no callback for [isKeyguardSuppressed], which means this can't be a + * flow, since it's ambiguous when we would query the latest suppression value. + */ + suspend fun isKeyguardEnabledAndNotSuppressed(): Boolean { + return isKeyguardEnabled.value && !isKeyguardSuppressed() + } + + /** + * Returns whether the lockscreen has been disabled ("suppressed") via "adb shell locksettings + * set-disabled". If suppressed, we'll ignore all signals that would typically result in showing + * the keyguard, regardless of the value of [isKeyguardEnabled]. + * + * It's extremely confusing to have [isKeyguardEnabled] not be the inverse of "is lockscreen + * disabled", so this method intentionally re-terms it as "suppressed". + * + * Note that if the lockscreen is currently showing when it's suppressed, it will remain visible + * until it's unlocked, at which point it will never re-appear until suppression is removed. + */ + suspend fun isKeyguardSuppressed( + userId: Int = selectedUserInteractor.getSelectedUserId() + ): Boolean { + // isLockScreenDisabled returns true whenever keyguard is not enabled, even if the adb + // command was not used to disable/suppress the lockscreen. To make these booleans as clear + // as possible, only return true if keyguard is suppressed when it otherwise would have + // been enabled. + return withContext(backgroundDispatcher) { + isKeyguardEnabled.value && lockPatternUtils.isLockScreenDisabled(userId) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt index 0ab3e5c0927f..ce84e71a3562 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt @@ -16,27 +16,16 @@ package com.android.systemui.keyguard.domain.interactor -import android.os.Bundle import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge -/** - * Emitted when we receive a [KeyguardLockWhileAwakeInteractor.onKeyguardServiceDoKeyguardTimeout] - * call. - * - * Includes a timestamp so it's not conflated by the StateFlow. - */ -data class KeyguardTimeoutWhileAwakeEvent(val timestamp: Long, val options: Bundle?) - /** The reason we're locking while awake, used for logging. */ enum class LockWhileAwakeReason(private val logReason: String) { LOCKDOWN("Lockdown initiated."), @@ -71,10 +60,8 @@ class KeyguardLockWhileAwakeInteractor constructor( biometricSettingsRepository: BiometricSettingsRepository, keyguardEnabledInteractor: KeyguardEnabledInteractor, + keyguardServiceLockNowInteractor: KeyguardServiceLockNowInteractor, ) { - /** Emits whenever a timeout event is received by [KeyguardService]. */ - private val timeoutEvents: MutableStateFlow<KeyguardTimeoutWhileAwakeEvent?> = - MutableStateFlow(null) /** Emits whenever the current user is in lockdown mode. */ private val inLockdown: Flow<LockWhileAwakeReason> = @@ -97,25 +84,19 @@ constructor( /** Emits whenever we should lock while the screen is on, for any reason. */ val lockWhileAwakeEvents: Flow<LockWhileAwakeReason> = merge( - inLockdown, - keyguardReenabled, - timeoutEvents.filterNotNull().map { - LockWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON - }, + // We're in lockdown, and the keyguard is enabled. If the keyguard is disabled, the + // lockdown button is hidden in the UI, but it's still possible to trigger lockdown in + // tests. + inLockdown + .filter { keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed() } + .map { LockWhileAwakeReason.LOCKDOWN }, + // The keyguard was re-enabled, and it was showing when it was originally disabled. + // Tests currently expect that if the keyguard is re-enabled, it will show even if it's + // suppressed, so we don't check for isKeyguardEnabledAndNotSuppressed() on this flow. + keyguardReenabled.map { LockWhileAwakeReason.KEYGUARD_REENABLED }, + // KeyguardService says we need to lock now, and the lockscreen is enabled. + keyguardServiceLockNowInteractor.lockNowEvents + .filter { keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed() } + .map { LockWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON }, ) - - /** - * Called by [KeyguardService] when it receives a doKeyguardTimeout() call. This indicates that - * the device locked while the screen was on. - * - * [options] appears to be no longer used, but we'll keep it in this interactor in case that - * turns out not to be true. - */ - fun onKeyguardServiceDoKeyguardTimeout(options: Bundle?) { - timeoutEvents.value = - KeyguardTimeoutWhileAwakeEvent( - timestamp = System.currentTimeMillis(), - options = options, - ) - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt new file mode 100644 index 000000000000..9ed53ea74316 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 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.systemui.keyguard.domain.interactor + +import android.annotation.SuppressLint +import android.os.Bundle +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch + +/** + * Emitted when we receive a [KeyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout] + * call. + */ +data class KeyguardLockNowEvent(val options: Bundle?) + +/** + * Logic around requests by [KeyguardService] to lock the device right now, even though the device + * is awake and not going to sleep. + * + * This can happen if WM#lockNow() is called, or if the screen is forced to stay awake but the lock + * timeout elapses. + * + * This is not the only way for the device to lock while the screen is on. The other cases, which do + * not directly involve [KeyguardService], are handled in [KeyguardLockWhileAwakeInteractor]. + */ +@SysUISingleton +class KeyguardServiceLockNowInteractor +@Inject +constructor(@Background val backgroundScope: CoroutineScope) { + + /** + * Emits whenever [KeyguardService] receives a call that indicates we should lock the device + * right now, even though the device is awake and not going to sleep. + * + * WARNING: This is only one of multiple reasons the device might need to lock while not going + * to sleep. Unless you're dealing with keyguard internals that specifically need to know that + * we're locking due to a call to doKeyguardTimeout, use + * [KeyguardLockWhileAwakeInteractor.lockWhileAwakeEvents]. + * + * This is fundamentally an event flow, hence the SharedFlow. + */ + @SuppressLint("SharedFlowCreation") + val lockNowEvents: MutableSharedFlow<KeyguardLockNowEvent> = MutableSharedFlow() + + /** + * Called by [KeyguardService] when it receives a doKeyguardTimeout() call. This indicates that + * the device locked while the screen was on. + * + * [options] appears to be no longer used, but we'll keep it in this interactor in case that + * turns out not to be true. + */ + fun onKeyguardServiceDoKeyguardTimeout(options: Bundle?) { + backgroundScope.launch { lockNowEvents.emit(KeyguardLockNowEvent(options = options)) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt index fbc7e2aecbdf..8641dfa5a183 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt @@ -25,6 +25,7 @@ import android.content.Intent import android.content.IntentFilter import android.provider.Settings import android.provider.Settings.Secure +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.widget.LockPatternUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -48,20 +49,21 @@ import javax.inject.Inject import kotlin.math.max import kotlin.math.min import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart /** * Logic related to the ability to wake directly to GONE from asleep (AOD/DOZING), without going * through LOCKSCREEN or a BOUNCER state. * * This is possible in the following scenarios: - * - The lockscreen is disabled, either from an app request (SUW does this), or by the security + * - The keyguard is not enabled, either from an app request (SUW does this), or by the security * "None" setting. + * - The keyguard was suppressed via adb. * - A biometric authentication event occurred while we were asleep (fingerprint auth, etc). This * specifically is referred to throughout the codebase as "wake and unlock". * - The screen timed out, but the "lock after screen timeout" duration has not elapsed. @@ -86,43 +88,44 @@ constructor( private val lockPatternUtils: LockPatternUtils, private val systemSettings: SystemSettings, private val selectedUserInteractor: SelectedUserInteractor, + keyguardEnabledInteractor: KeyguardEnabledInteractor, + keyguardServiceLockNowInteractor: KeyguardServiceLockNowInteractor, ) { /** - * Whether the lockscreen was disabled as of the last wake/sleep event, according to - * LockPatternUtils. - * - * This will always be true if [repository.isKeyguardServiceEnabled]=false, but it can also be - * true when the keyguard service is enabled if the lockscreen has been disabled via adb using - * the `adb shell locksettings set-disabled true` command, which is often done in tests. - * - * Unlike keyguardServiceEnabled, changes to this value should *not* immediately show or hide - * the keyguard. If the lockscreen is disabled in this way, it will just not show on the next - * sleep/wake. + * Whether the keyguard was suppressed as of the most recent wakefulness event or lockNow + * command. Keyguard suppression can only be queried (there is no callback available), and + * legacy code only queried the value in onStartedGoingToSleep and doKeyguardTimeout. Tests now + * depend on that behavior, so for now, we'll replicate it here. */ - private val isLockscreenDisabled: Flow<Boolean> = - powerInteractor.isAwake.map { isLockscreenDisabled() } + private val shouldSuppressKeyguard = + merge(powerInteractor.isAwake, keyguardServiceLockNowInteractor.lockNowEvents) + .map { keyguardEnabledInteractor.isKeyguardSuppressed() } + // Default to false, so that flows that combine this one emit prior to the first + // wakefulness emission. + .onStart { emit(false) } /** * Whether we can wake from AOD/DOZING directly to GONE, bypassing LOCKSCREEN/BOUNCER states. * * This is possible in the following cases: * - Keyguard is disabled, either from an app request or from security being set to "None". + * - Keyguard is suppressed, via adb locksettings. * - We're wake and unlocking (fingerprint auth occurred while asleep). * - We're allowed to ignore auth and return to GONE, due to timeouts not elapsing. */ val canWakeDirectlyToGone = combine( repository.isKeyguardEnabled, - isLockscreenDisabled, + shouldSuppressKeyguard, repository.biometricUnlockState, repository.canIgnoreAuthAndReturnToGone, ) { keyguardEnabled, - isLockscreenDisabled, + shouldSuppressKeyguard, biometricUnlockState, canIgnoreAuthAndReturnToGone -> - (!keyguardEnabled || isLockscreenDisabled) || + (!keyguardEnabled || shouldSuppressKeyguard) || BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode) || canIgnoreAuthAndReturnToGone } @@ -186,9 +189,9 @@ constructor( .sample( transitionInteractor.isCurrentlyIn( Scenes.Gone, - stateWithoutSceneContainer = KeyguardState.GONE + stateWithoutSceneContainer = KeyguardState.GONE, ), - ::Pair + ::Pair, ) .collect { (wakefulness, finishedInGone) -> // Save isAwake for use in onDreamingStarted/onDreamingStopped. @@ -260,7 +263,7 @@ constructor( delayedActionFilter, SYSTEMUI_PERMISSION, null /* scheduler */, - Context.RECEIVER_EXPORTED_UNAUDITED + Context.RECEIVER_EXPORTED_UNAUDITED, ) } @@ -282,7 +285,7 @@ constructor( context, 0, intent, - PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE, ) val time = systemClock.elapsedRealtime() + getCanIgnoreAuthAndReturnToGoneDuration() @@ -311,16 +314,6 @@ constructor( } /** - * Returns whether the lockscreen is disabled, either because the keyguard service is disabled - * or because an adb command has disabled the lockscreen. - */ - private fun isLockscreenDisabled( - userId: Int = selectedUserInteractor.getSelectedUserId() - ): Boolean { - return lockPatternUtils.isLockScreenDisabled(userId) - } - - /** * Returns the duration within which we can return to GONE without auth after a screen timeout * (or power button press, if lock instantly is disabled). * @@ -336,7 +329,7 @@ constructor( .getIntForUser( Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, KEYGUARD_CAN_IGNORE_AUTH_DURATION, - userId + userId, ) .toLong() @@ -352,7 +345,7 @@ constructor( .getIntForUser( Settings.System.SCREEN_OFF_TIMEOUT, KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT, - userId + userId, ) .toLong() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt index 007d2297e387..f88ed07046ab 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt @@ -16,18 +16,24 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.internal.widget.lockPatternUtils import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.user.domain.interactor.selectedUserInteractor val Kosmos.keyguardEnabledInteractor by Kosmos.Fixture { KeyguardEnabledInteractor( applicationCoroutineScope, + testDispatcher, keyguardRepository, biometricSettingsRepository, - keyguardDismissTransitionInteractor, + selectedUserInteractor, + lockPatternUtils, + { keyguardDismissTransitionInteractor }, internalTransitionInteractor = internalKeyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt index 39236c7c9fae..2423949cdacc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt @@ -24,5 +24,6 @@ val Kosmos.keyguardLockWhileAwakeInteractor by KeyguardLockWhileAwakeInteractor( biometricSettingsRepository = biometricSettingsRepository, keyguardEnabledInteractor = keyguardEnabledInteractor, + keyguardServiceLockNowInteractor = keyguardServiceLockNowInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt new file mode 100644 index 000000000000..29335c590a75 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 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.systemui.keyguard.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope + +val Kosmos.keyguardServiceLockNowInteractor by + Kosmos.Fixture { KeyguardServiceLockNowInteractor(backgroundScope = testScope) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt index 63e168d018bf..4aa132c1d3af 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt @@ -41,5 +41,7 @@ val Kosmos.keyguardWakeDirectlyToGoneInteractor by lockPatternUtils, fakeSettings, selectedUserInteractor, + keyguardEnabledInteractor, + keyguardServiceLockNowInteractor, ) } |