diff options
22 files changed, 587 insertions, 23 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index a5acf724dcff..ccddc9c7120f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -38,7 +38,7 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testScope -import com.android.systemui.scene.domain.interactor.sceneContainerStartable +import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.testKosmos diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt index 5f0f24dcca86..2d121502650e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -61,7 +62,8 @@ class TrustRepositoryTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - testScope = TestScope() + val testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) userRepository = FakeUserRepository() userRepository.setUserInfos(users) val logger = @@ -69,7 +71,13 @@ class TrustRepositoryTest : SysuiTestCase() { LogBuffer("TestBuffer", 1, mock(LogcatEchoTracker::class.java), false) ) underTest = - TrustRepositoryImpl(testScope.backgroundScope, userRepository, trustManager, logger) + TrustRepositoryImpl( + testScope.backgroundScope, + testDispatcher, + userRepository, + trustManager, + logger, + ) } fun TestScope.init() { @@ -275,4 +283,11 @@ class TrustRepositoryTest : SysuiTestCase() { userRepository.setSelectedUserInfo(users[1]) assertThat(trustUsuallyManaged).isFalse() } + + @Test + fun reportKeyguardShowingChanged() = + testScope.runTest { + underTest.reportKeyguardShowingChanged() + verify(trustManager).reportKeyguardShowingChanged() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 5b6fea5a52f0..d43d50ab0dc4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -42,9 +42,9 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneBackInteractor -import com.android.systemui.scene.domain.interactor.sceneContainerStartable import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver +import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.SceneFamilies import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 4d5d22c11f71..412505d50fc9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -58,9 +58,9 @@ import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.qs.footerActionsController import com.android.systemui.qs.footerActionsViewModelFactory import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter -import com.android.systemui.scene.domain.interactor.sceneContainerStartable import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver +import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.SceneFamilies import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt index e3108ad1b8f1..1f3454de14d7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartableTest.kt new file mode 100644 index 000000000000..695edafefdf3 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartableTest.kt @@ -0,0 +1,248 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.scene.domain.startable + +import android.content.pm.UserInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.internal.policy.IKeyguardStateCallback +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.fakeTrustRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.setSceneTransition +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableSceneContainer +class KeyguardStateCallbackStartableTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val underTest = kosmos.keyguardStateCallbackStartable + + @Test + fun addCallback_hydratesAllWithCurrentState() = + testScope.runTest { + val testState = setUpTest() + val callback = mockCallback() + + underTest.addCallback(callback) + runCurrent() + + with(testState) { + val captor = argumentCaptor<Boolean>() + verify(callback, atLeastOnce()).onShowingStateChanged(captor.capture(), eq(userId)) + assertThat(captor.lastValue).isEqualTo(isKeyguardShowing) + verify(callback, atLeastOnce()).onInputRestrictedStateChanged(captor.capture()) + assertThat(captor.lastValue).isEqualTo(isInputRestricted) + verify(callback, atLeastOnce()).onSimSecureStateChanged(captor.capture()) + assertThat(captor.lastValue).isEqualTo(isSimSecure) + verify(callback, atLeastOnce()).onTrustedChanged(captor.capture()) + assertThat(captor.lastValue).isEqualTo(isTrusted) + } + } + + @Test + fun hydrateKeyguardShowingState() = + testScope.runTest { + setUpTest(isKeyguardShowing = true) + val callback = mockCallback() + underTest.addCallback(callback) + runCurrent() + verify(callback, atLeastOnce()).onShowingStateChanged(eq(true), anyInt()) + + unlockDevice() + runCurrent() + + verify(callback).onShowingStateChanged(eq(false), anyInt()) + } + + @Test + fun hydrateInputRestrictedState() = + testScope.runTest { + setUpTest(isKeyguardShowing = true) + val callback = mockCallback() + underTest.addCallback(callback) + runCurrent() + val captor = argumentCaptor<Boolean>() + verify(callback, atLeastOnce()).onInputRestrictedStateChanged(captor.capture()) + assertThat(captor.lastValue).isTrue() + + unlockDevice() + runCurrent() + + verify(callback, atLeastOnce()).onInputRestrictedStateChanged(captor.capture()) + assertThat(captor.lastValue).isFalse() + } + + @Test + fun hydrateSimSecureState() = + testScope.runTest { + setUpTest(isSimSecure = false) + val callback = mockCallback() + underTest.addCallback(callback) + runCurrent() + val captor = argumentCaptor<Boolean>() + verify(callback, atLeastOnce()).onSimSecureStateChanged(captor.capture()) + assertThat(captor.lastValue).isFalse() + + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true + runCurrent() + + verify(callback, atLeastOnce()).onSimSecureStateChanged(captor.capture()) + assertThat(captor.lastValue).isTrue() + } + + @Test + fun notifyWhenKeyguardShowingChanged() = + testScope.runTest { + setUpTest(isKeyguardShowing = true) + val callback = mockCallback() + underTest.addCallback(callback) + runCurrent() + assertThat(kosmos.fakeTrustRepository.keyguardShowingChangeEventCount).isEqualTo(1) + + unlockDevice() + runCurrent() + + assertThat(kosmos.fakeTrustRepository.keyguardShowingChangeEventCount).isEqualTo(2) + } + + @Test + fun notifyWhenTrustChanged() = + testScope.runTest { + setUpTest(isTrusted = false) + val callback = mockCallback() + underTest.addCallback(callback) + runCurrent() + val captor = argumentCaptor<Boolean>() + verify(callback, atLeastOnce()).onTrustedChanged(captor.capture()) + assertThat(captor.lastValue).isFalse() + + kosmos.fakeTrustRepository.setCurrentUserTrusted(true) + runCurrent() + + verify(callback, atLeastOnce()).onTrustedChanged(captor.capture()) + assertThat(captor.lastValue).isTrue() + } + + private suspend fun TestScope.setUpTest( + isKeyguardShowing: Boolean = true, + userId: Int = selectedUser.id, + isInputRestricted: Boolean = true, + isSimSecure: Boolean = false, + isTrusted: Boolean = false, + ): TestState { + val testState = + TestState( + isKeyguardShowing = isKeyguardShowing, + userId = userId, + isInputRestricted = isInputRestricted, + isSimSecure = isSimSecure, + isTrusted = isTrusted, + ) + + if (isKeyguardShowing) { + lockDevice() + } else { + unlockDevice() + } + + kosmos.fakeUserRepository.setUserInfos(listOf(selectedUser)) + kosmos.fakeUserRepository.setSelectedUserInfo(selectedUser) + + if (isInputRestricted && !isKeyguardShowing) { + // TODO(b/348644111): add support for mNeedToReshowWhenReenabled + } else if (!isInputRestricted) { + assertWithMessage( + "If isInputRestricted is false, isKeyguardShowing must also be false!" + ) + .that(isKeyguardShowing) + .isFalse() + } + + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = isSimSecure + + kosmos.fakeTrustRepository.setCurrentUserTrusted(isTrusted) + + runCurrent() + + underTest.start() + + return testState + } + + private fun lockDevice() { + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Lockscreen)) + kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "") + } + + private fun unlockDevice() { + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone)) + kosmos.sceneInteractor.changeScene(Scenes.Gone, "") + } + + private fun mockCallback(): IKeyguardStateCallback { + return mock() + } + + private data class TestState( + val isKeyguardShowing: Boolean, + val userId: Int, + val isInputRestricted: Boolean, + val isSimSecure: Boolean, + val isTrusted: Boolean, + ) + + companion object { + private val selectedUser = + UserInfo( + /* id= */ 100, + /* name= */ "First user", + /* flags= */ 0, + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index e40c8eecca0f..9edc3af6bb4e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -51,7 +51,6 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState -import com.android.systemui.scene.domain.interactor.sceneContainerStartable import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt index 7d3075a9dd74..ed931bd4e66a 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt @@ -43,10 +43,11 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -68,7 +69,12 @@ constructor( mobileConnectionsRepository: MobileConnectionsRepository, ) { val subId: StateFlow<Int> = repository.subscriptionId - val isAnySimSecure: Flow<Boolean> = mobileConnectionsRepository.isAnySimSecure + val isAnySimSecure: StateFlow<Boolean> = + mobileConnectionsRepository.isAnySimSecure.stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = mobileConnectionsRepository.getIsAnySimSecure(), + ) val isLockedEsim: StateFlow<Boolean?> = repository.isLockedEsim val errorDialogMessage: StateFlow<String?> = repository.errorDialogMessage diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 209bc7adac33..c4b70d8013bf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -89,6 +89,7 @@ import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibil import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.shared.model.ScreenPowerState; import com.android.systemui.scene.domain.interactor.SceneInteractor; +import com.android.systemui.scene.domain.startable.KeyguardStateCallbackStartable; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.settings.DisplayTracker; @@ -122,6 +123,7 @@ public class KeyguardService extends Service { private final KeyguardInteractor mKeyguardInteractor; private final Lazy<SceneInteractor> mSceneInteractorLazy; private final Executor mMainExecutor; + private final Lazy<KeyguardStateCallbackStartable> mKeyguardStateCallbackStartableLazy; private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers, SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap, @@ -341,7 +343,8 @@ public class KeyguardService extends Service { Lazy<SceneInteractor> sceneInteractorLazy, @Main Executor mainExecutor, KeyguardInteractor keyguardInteractor, - KeyguardEnabledInteractor keyguardEnabledInteractor) { + KeyguardEnabledInteractor keyguardEnabledInteractor, + Lazy<KeyguardStateCallbackStartable> keyguardStateCallbackStartableLazy) { super(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher; @@ -353,6 +356,7 @@ public class KeyguardService extends Service { mKeyguardInteractor = keyguardInteractor; mSceneInteractorLazy = sceneInteractorLazy; mMainExecutor = mainExecutor; + mKeyguardStateCallbackStartableLazy = keyguardStateCallbackStartableLazy; if (KeyguardWmStateRefactor.isEnabled()) { WindowManagerLockscreenVisibilityViewBinder.bind( @@ -440,7 +444,11 @@ public class KeyguardService extends Service { public void addStateMonitorCallback(IKeyguardStateCallback callback) { trace("addStateMonitorCallback"); checkPermission(); - mKeyguardViewMediator.addStateMonitorCallback(callback); + if (SceneContainerFlag.isEnabled()) { + mKeyguardStateCallbackStartableLazy.get().addCallback(callback); + } else { + mKeyguardViewMediator.addStateMonitorCallback(callback); + } } @Override // Binder interface diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index b70dbe240ce0..36b7ed26158f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -3801,6 +3801,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } private void notifyDefaultDisplayCallbacks(boolean showing) { + if (SceneContainerFlag.isEnabled()) { + return; + } + // TODO(b/140053364) whitelistIpcs(() -> { int size = mKeyguardStateCallbacks.size(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt index 6522439460e4..bd5d096b4f6a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt @@ -23,11 +23,13 @@ import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLoggin import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow 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.shared.model.ActiveUnlockModel import com.android.systemui.keyguard.shared.model.TrustManagedModel import com.android.systemui.keyguard.shared.model.TrustModel import com.android.systemui.user.data.repository.UserRepository import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose @@ -44,6 +46,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext /** Encapsulates any state relevant to trust agents and trust grants. */ interface TrustRepository { @@ -51,7 +54,7 @@ interface TrustRepository { val isCurrentUserTrustUsuallyManaged: StateFlow<Boolean> /** Flow representing whether the current user is trusted. */ - val isCurrentUserTrusted: Flow<Boolean> + val isCurrentUserTrusted: StateFlow<Boolean> /** Flow representing whether active unlock is running for the current user. */ val isCurrentUserActiveUnlockRunning: Flow<Boolean> @@ -63,6 +66,9 @@ interface TrustRepository { /** A trust agent is requesting to dismiss the keyguard from a trust change. */ val trustAgentRequestingToDismissKeyguard: Flow<TrustModel> + + /** Reports a keyguard visibility change. */ + suspend fun reportKeyguardShowingChanged() } @OptIn(ExperimentalCoroutinesApi::class) @@ -71,6 +77,7 @@ class TrustRepositoryImpl @Inject constructor( @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, private val userRepository: UserRepository, private val trustManager: TrustManager, private val logger: TrustRepositoryLogger, @@ -191,11 +198,26 @@ constructor( private fun isUserTrustManaged(userId: Int) = trustManagedForUser[userId]?.isTrustManaged ?: false - override val isCurrentUserTrusted: Flow<Boolean> + override val isCurrentUserTrusted: StateFlow<Boolean> get() = combine(trust, userRepository.selectedUserInfo, ::Pair) - .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false } + .map { isCurrentUserTrusted(it.second.id) } .distinctUntilChanged() .onEach { logger.isCurrentUserTrusted(it) } .onStart { emit(false) } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = isCurrentUserTrusted(), + ) + + private fun isCurrentUserTrusted( + selectedUserId: Int = userRepository.getSelectedUserInfo().id + ): Boolean { + return latestTrustModelForUser[selectedUserId]?.isTrusted ?: false + } + + override suspend fun reportKeyguardShowingChanged() { + withContext(backgroundDispatcher) { trustManager.reportKeyguardShowingChanged() } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt index 2ff6e165293b..73248bbec6ca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt @@ -17,14 +17,21 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.TrustRepository import javax.inject.Inject -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch /** Encapsulates any state relevant to trust agents and trust grants. */ @SysUISingleton -class TrustInteractor @Inject constructor(repository: TrustRepository) { +class TrustInteractor +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + private val repository: TrustRepository, +) { /** * Whether the current user has a trust agent enabled. This is true if the user has at least one * trust agent enabled in settings. @@ -39,5 +46,10 @@ class TrustInteractor @Inject constructor(repository: TrustRepository) { val isTrustAgentCurrentlyAllowed: StateFlow<Boolean> = repository.isCurrentUserTrustManaged /** Whether the current user is trusted by any of the enabled trust agents. */ - val isTrusted: Flow<Boolean> = repository.isCurrentUserTrusted + val isTrusted: StateFlow<Boolean> = repository.isCurrentUserTrusted + + /** Reports a keyguard visibility change. */ + fun reportKeyguardShowingChanged() { + applicationScope.launch { repository.reportKeyguardShowingChanged() } + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt index 08462d79b2fb..6e8997346fc4 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt @@ -23,6 +23,7 @@ import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInte import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule import com.android.systemui.scene.domain.resolver.NotifShadeSceneFamilyResolverModule import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolverModule +import com.android.systemui.scene.domain.startable.KeyguardStateCallbackStartable import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.domain.startable.ScrimStartable import com.android.systemui.scene.domain.startable.StatusBarStartable @@ -72,6 +73,11 @@ interface KeyguardlessSceneContainerFrameworkModule { @Binds @IntoMap + @ClassKey(KeyguardStateCallbackStartable::class) + fun keyguardStateCallbackStartable(impl: KeyguardStateCallbackStartable): CoreStartable + + @Binds + @IntoMap @ClassKey(WindowRootViewVisibilityInteractor::class) fun bindWindowRootViewVisibilityInteractor( impl: WindowRootViewVisibilityInteractor diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index 17dc9a52f9b7..7d63b4ce0044 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -24,6 +24,7 @@ import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInte import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule import com.android.systemui.scene.domain.resolver.NotifShadeSceneFamilyResolverModule import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolverModule +import com.android.systemui.scene.domain.startable.KeyguardStateCallbackStartable import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.domain.startable.ScrimStartable import com.android.systemui.scene.domain.startable.StatusBarStartable @@ -78,6 +79,11 @@ interface SceneContainerFrameworkModule { @Binds @IntoMap + @ClassKey(KeyguardStateCallbackStartable::class) + fun keyguardStateCallbackStartable(impl: KeyguardStateCallbackStartable): CoreStartable + + @Binds + @IntoMap @ClassKey(WindowRootViewVisibilityInteractor::class) fun bindWindowRootViewVisibilityInteractor( impl: WindowRootViewVisibilityInteractor diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt new file mode 100644 index 000000000000..6d1c1a7b5bfe --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt @@ -0,0 +1,170 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.scene.domain.startable + +import android.os.DeadObjectException +import android.os.RemoteException +import com.android.internal.policy.IKeyguardStateCallback +import com.android.systemui.CoreStartable +import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.keyguard.domain.interactor.TrustInteractor +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** Keeps all [IKeyguardStateCallback]s hydrated with the latest state. */ +@SysUISingleton +class KeyguardStateCallbackStartable +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val sceneInteractor: SceneInteractor, + private val selectedUserInteractor: SelectedUserInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, + private val simBouncerInteractor: SimBouncerInteractor, + private val trustInteractor: TrustInteractor, +) : CoreStartable { + + private val callbacks = mutableListOf<IKeyguardStateCallback>() + + override fun start() { + if (!SceneContainerFlag.isEnabled) { + return + } + + hydrateKeyguardShowingAndInputRestrictionStates() + hydrateSimSecureState() + notifyWhenKeyguardShowingChanged() + notifyWhenTrustChanged() + } + + fun addCallback(callback: IKeyguardStateCallback) { + SceneContainerFlag.assertInNewMode() + + callbacks.add(callback) + + applicationScope.launch(backgroundDispatcher) { + callback.onShowingStateChanged( + !deviceEntryInteractor.isDeviceEntered.value, + selectedUserInteractor.getSelectedUserId(), + ) + callback.onTrustedChanged(trustInteractor.isTrusted.value) + callback.onSimSecureStateChanged(simBouncerInteractor.isAnySimSecure.value) + // TODO(b/348644111): add support for mNeedToReshowWhenReenabled + callback.onInputRestrictedStateChanged(!deviceEntryInteractor.isDeviceEntered.value) + } + } + + private fun hydrateKeyguardShowingAndInputRestrictionStates() { + applicationScope.launch { + combine( + selectedUserInteractor.selectedUser, + deviceEntryInteractor.isDeviceEntered, + ::Pair + ) + .collectLatest { (selectedUserId, isDeviceEntered) -> + val iterator = callbacks.iterator() + withContext(backgroundDispatcher) { + while (iterator.hasNext()) { + val callback = iterator.next() + try { + callback.onShowingStateChanged(!isDeviceEntered, selectedUserId) + // TODO(b/348644111): add support for mNeedToReshowWhenReenabled + callback.onInputRestrictedStateChanged(!isDeviceEntered) + } catch (e: RemoteException) { + if (e is DeadObjectException) { + iterator.remove() + } + } + } + } + } + } + } + + private fun hydrateSimSecureState() { + applicationScope.launch { + simBouncerInteractor.isAnySimSecure.collectLatest { isSimSecured -> + val iterator = callbacks.iterator() + withContext(backgroundDispatcher) { + while (iterator.hasNext()) { + val callback = iterator.next() + try { + callback.onSimSecureStateChanged(isSimSecured) + } catch (e: RemoteException) { + if (e is DeadObjectException) { + iterator.remove() + } + } + } + } + } + } + } + + private fun notifyWhenKeyguardShowingChanged() { + applicationScope.launch { + // This is equivalent to isDeviceEntered but it waits for the full transition animation + // to finish before emitting a new value and not just for the current scene to be + // switched. + sceneInteractor.transitionState + .filter { it.isIdle(Scenes.Gone) || it.isIdle(Scenes.Lockscreen) } + .map { it.isIdle(Scenes.Lockscreen) } + .distinctUntilChanged() + .collectLatest { trustInteractor.reportKeyguardShowingChanged() } + } + } + + private fun notifyWhenTrustChanged() { + applicationScope.launch { + trustInteractor.isTrusted.collectLatest { isTrusted -> + val iterator = callbacks.iterator() + withContext(backgroundDispatcher) { + while (iterator.hasNext()) { + val callback = iterator.next() + try { + callback.onTrustedChanged(isTrusted) + } catch (e: RemoteException) { + if (e is DeadObjectException) { + iterator.remove() + } + } + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 1e689bd11fa3..218853d7a1fb 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -22,6 +22,7 @@ import android.app.StatusBarManager import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.internal.logging.UiEventLogger +import com.android.internal.policy.IKeyguardStateCallback import com.android.systemui.CoreStartable import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel @@ -127,6 +128,8 @@ constructor( private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() + private val keyguardStateCallbacks = mutableListOf<IKeyguardStateCallback>() + override fun start() { if (SceneContainerFlag.isEnabled) { sceneLogger.logFrameworkEnabled(isEnabled = true) diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt index cf33c4a3fafb..6c63c9762e5e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt @@ -39,13 +39,14 @@ object SceneContainerFlag { inline val isEnabled get() = sceneContainer() && // mainAconfigFlag - ComposeLockscreen.isEnabled && + ComposeLockscreen.isEnabled && KeyguardBottomAreaRefactor.isEnabled && KeyguardWmStateRefactor.isEnabled && MigrateClocksToBlueprint.isEnabled && NotificationsHeadsUpRefactor.isEnabled && PredictiveBackSysUiFlag.isEnabled && DeviceEntryUdfpsRefactor.isEnabled + // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer /** The main aconfig flag. */ @@ -91,6 +92,14 @@ object SceneContainerFlag { @JvmStatic inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, DESCRIPTION) + /** + * Called to ensure the new code is only run when the flag is enabled. This will throw an + * exception if the flag is disabled to ensure that the refactor author catches issues in + * testing. + */ + @JvmStatic + inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, DESCRIPTION) + /** Returns a developer-readable string that describes the current requirement list. */ @JvmStatic fun requirementDescription(): String { diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt index d32d12c83a8d..a4936e63df8f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt @@ -37,7 +37,7 @@ import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.motion.createSysUiComposeMotionTestRule -import com.android.systemui.scene.domain.interactor.sceneContainerStartable +import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.testKosmos import org.junit.Before import org.junit.Rule diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt index cd83c2fa480c..39a1a639ced9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt @@ -33,9 +33,10 @@ class FakeTrustRepository @Inject constructor() : TrustRepository { private val _isTrustUsuallyManaged = MutableStateFlow(false) override val isCurrentUserTrustUsuallyManaged: StateFlow<Boolean> get() = _isTrustUsuallyManaged + private val _isCurrentUserTrusted = MutableStateFlow(false) - override val isCurrentUserTrusted: Flow<Boolean> - get() = _isCurrentUserTrusted + override val isCurrentUserTrusted: StateFlow<Boolean> + get() = _isCurrentUserTrusted.asStateFlow() private val _isCurrentUserActiveUnlockAvailable = MutableStateFlow(false) override val isCurrentUserActiveUnlockRunning: StateFlow<Boolean> = @@ -48,6 +49,13 @@ class FakeTrustRepository @Inject constructor() : TrustRepository { private val _requestDismissKeyguard = MutableStateFlow(TrustModel(false, 0, TrustGrantFlags(0))) override val trustAgentRequestingToDismissKeyguard: Flow<TrustModel> = _requestDismissKeyguard + var keyguardShowingChangeEventCount: Int = 0 + private set + + override suspend fun reportKeyguardShowingChanged() { + keyguardShowingChangeEventCount++ + } + fun setCurrentUserTrusted(trust: Boolean) { _isCurrentUserTrusted.value = trust } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt index 0ebf1642478e..d60326c6c367 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt @@ -19,5 +19,11 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.keyguard.data.repository.trustRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope -val Kosmos.trustInteractor by Fixture { TrustInteractor(repository = trustRepository) } +val Kosmos.trustInteractor by Fixture { + TrustInteractor( + applicationScope = applicationCoroutineScope, + repository = trustRepository, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartableKosmos.kt new file mode 100644 index 000000000000..f9111bfb3dcf --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartableKosmos.kt @@ -0,0 +1,39 @@ +/* + * 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.scene.domain.startable + +import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.keyguard.domain.interactor.trustInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.user.domain.interactor.selectedUserInteractor + +val Kosmos.keyguardStateCallbackStartable by Fixture { + KeyguardStateCallbackStartable( + applicationScope = applicationCoroutineScope, + backgroundDispatcher = testDispatcher, + sceneInteractor = sceneInteractor, + selectedUserInteractor = selectedUserInteractor, + deviceEntryInteractor = deviceEntryInteractor, + simBouncerInteractor = simBouncerInteractor, + trustInteractor = trustInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt index cf18c0e295ea..8b887d32f3c1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.scene.domain.interactor +package com.android.systemui.scene.domain.startable import com.android.internal.logging.uiEventLogger import com.android.systemui.authentication.domain.interactor.authenticationInteractor @@ -32,7 +32,9 @@ import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testScope import com.android.systemui.model.sysUiState import com.android.systemui.power.domain.interactor.powerInteractor -import com.android.systemui.scene.domain.startable.SceneContainerStartable +import com.android.systemui.scene.domain.interactor.sceneBackInteractor +import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.session.shared.shadeSessionStorage import com.android.systemui.scene.shared.logger.sceneLogger import com.android.systemui.settings.displayTracker |