diff options
14 files changed, 216 insertions, 66 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt index 3eb08004ae61..f063655b9f86 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.communal.data.db import android.content.ComponentName import android.os.UserHandle -import android.os.UserManager +import android.os.userManager import androidx.sqlite.db.SupportSQLiteDatabase import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -27,10 +27,13 @@ import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason. import com.android.systemui.communal.shared.model.SpanValue import com.android.systemui.communal.widgets.CommunalWidgetHost import com.android.systemui.kosmos.applicationCoroutineScope -import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.log.logcatLogBuffer import com.android.systemui.testKosmos -import kotlinx.coroutines.test.runCurrent +import com.android.systemui.user.data.repository.FakeUserRepository.Companion.MAIN_USER_ID +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.user.domain.interactor.userLockedInteractor import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -38,6 +41,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never @@ -46,8 +50,7 @@ import org.mockito.kotlin.verify @SmallTest @RunWith(AndroidJUnit4::class) class DefaultWidgetPopulationTest : SysuiTestCase() { - private val kosmos = testKosmos() - private val testScope = kosmos.testScope + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val communalWidgetHost = mock<CommunalWidgetHost> { @@ -57,11 +60,6 @@ class DefaultWidgetPopulationTest : SysuiTestCase() { private val communalWidgetDao = mock<CommunalWidgetDao>() private val database = mock<SupportSQLiteDatabase>() private val mainUser = UserHandle(0) - private val userManager = - mock<UserManager> { - on { mainUser }.thenReturn(mainUser) - on { getUserSerialNumber(0) }.thenReturn(0) - } private val defaultWidgets = arrayOf( @@ -74,6 +72,7 @@ class DefaultWidgetPopulationTest : SysuiTestCase() { @Before fun setUp() { + kosmos.fakeUserRepository.setUserUnlocked(MAIN_USER_ID, true) underTest = DefaultWidgetPopulation( bgScope = kosmos.applicationCoroutineScope, @@ -81,32 +80,45 @@ class DefaultWidgetPopulationTest : SysuiTestCase() { communalWidgetDaoProvider = { communalWidgetDao }, defaultWidgets = defaultWidgets, logBuffer = logcatLogBuffer("DefaultWidgetPopulationTest"), - userManager = userManager, + userManager = kosmos.userManager, + userLockedInteractor = kosmos.userLockedInteractor, ) } @Test + fun testNoInteractionUntilMainUserUnlocked() = + kosmos.runTest { + kosmos.fakeUserRepository.setUserUnlocked(MAIN_USER_ID, false) + // Database created + underTest.onCreate(database) + verify(communalWidgetHost, never()) + .allocateIdAndBindWidget(provider = any(), user = any()) + kosmos.fakeUserRepository.setUserUnlocked(MAIN_USER_ID, true) + verify(communalWidgetHost, atLeastOnce()) + .allocateIdAndBindWidget(provider = any(), user = any()) + } + + @Test fun testPopulateDefaultWidgetsWhenDatabaseCreated() = - testScope.runTest { + kosmos.runTest { // Database created underTest.onCreate(database) - runCurrent() // Verify default widgets bound verify(communalWidgetHost) .allocateIdAndBindWidget( provider = eq(ComponentName.unflattenFromString(defaultWidgets[0])!!), - user = eq(mainUser), + user = eq(UserHandle(MAIN_USER_ID)), ) verify(communalWidgetHost) .allocateIdAndBindWidget( provider = eq(ComponentName.unflattenFromString(defaultWidgets[1])!!), - user = eq(mainUser), + user = eq(UserHandle(MAIN_USER_ID)), ) verify(communalWidgetHost) .allocateIdAndBindWidget( provider = eq(ComponentName.unflattenFromString(defaultWidgets[2])!!), - user = eq(mainUser), + user = eq(UserHandle(MAIN_USER_ID)), ) // Verify default widgets added in database @@ -138,13 +150,12 @@ class DefaultWidgetPopulationTest : SysuiTestCase() { @Test fun testSkipDefaultWidgetsPopulation() = - testScope.runTest { + kosmos.runTest { // Skip default widgets population underTest.skipDefaultWidgetsPopulation(RESTORED_FROM_BACKUP) // Database created underTest.onCreate(database) - runCurrent() // Verify no widget bounded or added to the database verify(communalWidgetHost, never()).allocateIdAndBindWidget(any(), any()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index c3cc3e66f81f..8424746f3db5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -75,6 +75,7 @@ import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.fakeUserTracker import com.android.systemui.statusbar.phone.fakeManagedProfileController import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -163,12 +164,12 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test - fun isCommunalAvailable_storageUnlockedAndMainUser_true() = + fun isCommunalAvailable_mainUserUnlockedAndMainUser_true() = kosmos.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true) fakeUserRepository.setSelectedUserInfo(mainUser) fakeKeyguardRepository.setKeyguardShowing(true) @@ -176,12 +177,12 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test - fun isCommunalAvailable_storageLockedAndMainUser_false() = + fun isCommunalAvailable_mainUserLockedAndMainUser_false() = kosmos.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - fakeKeyguardRepository.setIsEncryptedOrLockdown(true) + fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, false) fakeUserRepository.setSelectedUserInfo(mainUser) fakeKeyguardRepository.setKeyguardShowing(true) @@ -189,12 +190,12 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test - fun isCommunalAvailable_storageUnlockedAndSecondaryUser_false() = + fun isCommunalAvailable_mainUserUnlockedAndSecondaryUser_false() = kosmos.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true) fakeUserRepository.setSelectedUserInfo(secondaryUser) fakeKeyguardRepository.setKeyguardShowing(true) @@ -207,7 +208,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true) fakeUserRepository.setSelectedUserInfo(mainUser) fakeKeyguardRepository.setKeyguardShowing(true) @@ -222,7 +223,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, false) fakeUserRepository.setSelectedUserInfo(mainUser) fakeKeyguardRepository.setKeyguardShowing(true) @@ -1282,7 +1283,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showCommunalWhileCharging() = kosmos.runTest { - fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true) fakeUserRepository.setSelectedUserInfo(mainUser) fakeKeyguardRepository.setKeyguardShowing(true) fakeSettings.putIntForUser( @@ -1302,7 +1303,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showCommunalWhilePosturedAndCharging() = kosmos.runTest { - fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true) fakeUserRepository.setSelectedUserInfo(mainUser) fakeKeyguardRepository.setKeyguardShowing(true) fakeSettings.putIntForUser( @@ -1323,7 +1324,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showCommunalWhileDocked() = kosmos.runTest { - fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true) fakeUserRepository.setSelectedUserInfo(mainUser) fakeKeyguardRepository.setKeyguardShowing(true) fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, 1, mainUser.id) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index dbdd7fb2773a..85155157eda2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -203,7 +203,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { // Keyguard showing, storage unlocked, main user, and tutorial not started. keyguardRepository.setKeyguardShowing(true) keyguardRepository.setKeyguardOccluded(false) - keyguardRepository.setIsEncryptedOrLockdown(false) + userRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true) setIsMainUser(true) tutorialRepository.setTutorialSettingState( Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt index 95681941a1c1..c15f797aad5d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt @@ -37,7 +37,9 @@ import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.settings.fakeUserTracker import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.FakeUserRepository.Companion.MAIN_USER_ID import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.user.domain.interactor.userLockedInteractor import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat @@ -91,6 +93,7 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { kosmos.testDispatcher, { widgetManager }, helper, + kosmos.userLockedInteractor, ) } @@ -269,6 +272,7 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { // Binding to the service does not require keyguard showing setCommunalAvailable(true, setKeyguardShowing = false) + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) runCurrent() verify(widgetManager).register() @@ -283,7 +287,7 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { setKeyguardShowing: Boolean = true, ) = with(kosmos) { - fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setUserUnlocked(MAIN_USER_ID, true) fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO) if (setKeyguardShowing) { fakeKeyguardRepository.setKeyguardShowing(true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 3ca1f5c0dd30..bafa8cf05a7f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.user.data.repository import android.app.admin.devicePolicyManager +import android.content.Intent import android.content.pm.UserInfo import android.internal.statusbar.fakeStatusBarService import android.os.UserHandle @@ -27,6 +28,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher @@ -50,6 +52,8 @@ import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock @SmallTest @RunWith(AndroidJUnit4::class) @@ -172,6 +176,39 @@ class UserRepositoryImplTest : SysuiTestCase() { } @Test + fun userUnlockedFlow_tracksBroadcastedChanges() = + testScope.runTest { + val userHandle: UserHandle = mock() + underTest = create(testScope.backgroundScope) + val latest by collectLastValue(underTest.isUserUnlocked(userHandle)) + whenever(manager.isUserUnlocked(eq(userHandle))).thenReturn(false) + broadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_USER_UNLOCKED), + ) + + assertThat(latest).isFalse() + + whenever(manager.isUserUnlocked(eq(userHandle))).thenReturn(true) + broadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_USER_UNLOCKED), + ) + + assertThat(latest).isTrue() + } + + @Test + fun userUnlockedFlow_initialValueReported() = + testScope.runTest { + val userHandle: UserHandle = mock() + underTest = create(testScope.backgroundScope) + whenever(manager.isUserUnlocked(eq(userHandle))).thenReturn(true) + val latest by collectLastValue(underTest.isUserUnlocked(userHandle)) + assertThat(latest).isTrue() + } + + @Test fun refreshUsers_sortsByCreationTime_guestUserLast() = testScope.runTest { underTest = create(testScope.backgroundScope) diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt index 3907a37cd5d9..f304b97a4ffe 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt @@ -37,11 +37,13 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog +import com.android.systemui.user.domain.interactor.UserLockedInteractor import javax.inject.Inject import javax.inject.Named import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first /** * Callback that will be invoked when the Room database is created. Then the database will be @@ -57,6 +59,7 @@ constructor( @Named(DEFAULT_WIDGETS) private val defaultWidgets: Array<String>, @CommunalLog logBuffer: LogBuffer, private val userManager: UserManager, + private val userLockedInteractor: UserLockedInteractor, ) : RoomDatabase.Callback() { companion object { private const val TAG = "DefaultWidgetPopulation" @@ -79,38 +82,36 @@ constructor( } bgScope.launch { - // Default widgets should be associated with the main user. - val user = userManager.mainUser - - if (user == null) { - logger.w( - "Skipped populating default widgets. Reason: device does not have a main user" - ) - return@launch - } + userLockedInteractor.isUserUnlocked(userManager.mainUser).first { it } + populateDefaultWidgets() + } + } - val userSerialNumber = userManager.getUserSerialNumber(user.identifier) - - defaultWidgets.forEachIndexed { index, name -> - val provider = ComponentName.unflattenFromString(name) - provider?.let { - val id = communalWidgetHost.allocateIdAndBindWidget(provider, user) - id?.let { - communalWidgetDaoProvider - .get() - .addWidget( - widgetId = id, - componentName = name, - rank = index, - userSerialNumber = userSerialNumber, - spanY = SpanValue.Fixed(3), - ) - } + private fun populateDefaultWidgets() { + // Default widgets should be associated with the main user. + val user = userManager.mainUser ?: return + + val userSerialNumber = userManager.getUserSerialNumber(user.identifier) + + defaultWidgets.forEachIndexed { index, name -> + val provider = ComponentName.unflattenFromString(name) + provider?.let { + val id = communalWidgetHost.allocateIdAndBindWidget(provider, user) + id?.let { + communalWidgetDaoProvider + .get() + .addWidget( + widgetId = id, + componentName = name, + rank = index, + userSerialNumber = userSerialNumber, + spanY = SpanValue.Fixed(3), + ) } } - - logger.i("Populated default widgets in the database.") } + + logger.i("Populated default widgets in the database.") } /** diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index de55c92e84f9..6dab32a66c94 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -69,6 +69,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.ManagedProfileController +import com.android.systemui.user.domain.interactor.UserLockedInteractor import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not import com.android.systemui.util.kotlin.emitOnStart @@ -127,6 +128,7 @@ constructor( private val batteryInteractor: BatteryInteractor, private val dockManager: DockManager, private val posturingInteractor: PosturingInteractor, + private val userLockedInteractor: UserLockedInteractor, ) { private val logger = Logger(logBuffer, "CommunalInteractor") @@ -162,7 +164,7 @@ constructor( val isCommunalAvailable: Flow<Boolean> = allOf( communalSettingsInteractor.isCommunalEnabled, - not(keyguardInteractor.isEncryptedOrLockdown), + userLockedInteractor.isUserUnlocked(userManager.mainUser), keyguardInteractor.isKeyguardShowing, ) .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt index dec7ba3a3eb1..06a14eaa5c85 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt @@ -26,6 +26,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.settings.UserTracker +import com.android.systemui.user.domain.interactor.UserLockedInteractor import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not @@ -58,6 +59,7 @@ constructor( @Main private val uiDispatcher: CoroutineDispatcher, private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>, private val glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper, + private val userLockedInteractor: UserLockedInteractor, ) : CoreStartable { private val appWidgetHost by lazy { appWidgetHostLazy.get() } diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index ad97b21ea60b..c960b5525d96 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -21,6 +21,7 @@ import android.annotation.SuppressLint import android.annotation.UserIdInt import android.app.admin.DevicePolicyManager import android.content.Context +import android.content.Intent import android.content.IntentFilter import android.content.pm.UserInfo import android.content.res.Resources @@ -84,6 +85,9 @@ interface UserRepository { /** [UserInfo] of the currently-selected user. */ val selectedUserInfo: Flow<UserInfo> + /** Tracks whether the main user is unlocked. */ + fun isUserUnlocked(userHandle: UserHandle?): Flow<Boolean> + /** User ID of the main user. */ val mainUserId: Int @@ -284,6 +288,18 @@ constructor( } .stateIn(applicationScope, SharingStarted.Eagerly, false) + override fun isUserUnlocked(userHandle: UserHandle?): Flow<Boolean> = + broadcastDispatcher + .broadcastFlow(IntentFilter(Intent.ACTION_USER_UNLOCKED)) + .map { getUnlockedState(userHandle) } + .onStart { emit(getUnlockedState(userHandle)) } + + private suspend fun getUnlockedState(userHandle: UserHandle?): Boolean { + return withContext(backgroundDispatcher) { + userHandle?.let { user -> manager.isUserUnlocked(user) } ?: false + } + } + @SuppressLint("MissingPermission") override val isLogoutToSystemUserEnabled: StateFlow<Boolean> = selectedUser diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt new file mode 100644 index 000000000000..ef29a387e06f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2025 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.user.domain.interactor + +import android.os.UserHandle +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.user.data.repository.UserRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class UserLockedInteractor @Inject constructor(val userRepository: UserRepository) { + fun isUserUnlocked(userHandle: UserHandle?): Flow<Boolean> = + userRepository.isUserUnlocked(userHandle) +} diff --git a/packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.kt index c936b914f44e..26618484669b 100644 --- a/packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.kt @@ -17,6 +17,15 @@ package android.os import com.android.systemui.kosmos.Kosmos -import com.android.systemui.util.mockito.mock +import com.android.systemui.user.data.repository.FakeUserRepository.Companion.MAIN_USER_ID +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever -var Kosmos.userManager by Kosmos.Fixture { mock<UserManager>() } +var Kosmos.userManager by + Kosmos.Fixture { + mock<UserManager> { + whenever(it.mainUser).thenReturn(UserHandle(MAIN_USER_ID)) + whenever(it.getUserSerialNumber(eq(MAIN_USER_ID))).thenReturn(0) + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index b0a6de1f931a..0f21a16147f0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -42,7 +42,9 @@ import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.settings.userTracker import com.android.systemui.statusbar.phone.fakeManagedProfileController +import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.user.domain.interactor.userLockedInteractor import com.android.systemui.util.mockito.mock val Kosmos.communalInteractor by Fixture { @@ -70,6 +72,7 @@ val Kosmos.communalInteractor by Fixture { batteryInteractor = batteryInteractor, dockManager = dockManager, posturingInteractor = posturingInteractor, + userLockedInteractor = userLockedInteractor, ) } @@ -98,10 +101,8 @@ suspend fun Kosmos.setCommunalV2Enabled(enabled: Boolean) { suspend fun Kosmos.setCommunalAvailable(available: Boolean) { setCommunalEnabled(available) - with(fakeKeyguardRepository) { - setIsEncryptedOrLockdown(!available) - setKeyguardShowing(available) - } + fakeKeyguardRepository.setKeyguardShowing(available) + fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, available) } suspend fun Kosmos.setCommunalV2Available(available: Boolean) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index 85d582a27faf..145bb93d9cb0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update import kotlinx.coroutines.yield @SysUISingleton @@ -76,6 +77,11 @@ class FakeUserRepository @Inject constructor() : UserRepository { override val isLogoutToSystemUserEnabled: StateFlow<Boolean> = _isLogoutToSystemUserEnabled.asStateFlow() + private val _userUnlockedState = MutableStateFlow(emptyMap<UserHandle, Boolean>()) + + override fun isUserUnlocked(userHandle: UserHandle?): Flow<Boolean> = + _userUnlockedState.map { it[userHandle] ?: false } + override var mainUserId: Int = MAIN_USER_ID override var lastSelectedNonGuestUserId: Int = mainUserId @@ -176,6 +182,14 @@ class FakeUserRepository @Inject constructor() : UserRepository { fun setGuestUserAutoCreated(value: Boolean) { _isGuestUserAutoCreated = value } + + fun setUserUnlocked(userId: Int, unlocked: Boolean) { + setUserUnlocked(UserHandle(userId), unlocked) + } + + fun setUserUnlocked(userHandle: UserHandle, unlocked: Boolean) { + _userUnlockedState.update { it + (userHandle to unlocked) } + } } @Module diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt new file mode 100644 index 000000000000..fd955089cdc7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2025 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.user.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.user.data.repository.userRepository + +val Kosmos.userLockedInteractor by + Kosmos.Fixture { UserLockedInteractor(userRepository = userRepository) } |