summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Bryce Lee <brycelee@google.com> 2025-02-06 21:57:49 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-02-06 21:57:49 -0800
commitca7d0c749352f76ccf27f4c405e1a37322976c4a (patch)
tree1dba56b3c03eb880daa2efe2f259ad57900ac879
parent857fb87709dac9dbb22523312ab81e240331a55a (diff)
parent8992726accfb985fcf1a8a6e68bd0f87c04cb400 (diff)
Merge "Block communal widget actions until main user is unlocked" into main
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt47
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/android/os/UserManagerKosmos.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt23
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) }