summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt176
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt95
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt2
15 files changed, 296 insertions, 211 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index b70486473672..c073b79ba5a3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -67,8 +67,9 @@ fun CommunalContainer(
// Don't show hub mode UI if keyguard is not present. This is important since we're in the
// shade, which can be opened from many locations.
val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false)
+ val isCommunalAvailable by viewModel.isCommunalAvailable.collectAsState()
- if (!isKeyguardShowing) {
+ if (!isKeyguardShowing || !isCommunalAvailable) {
return
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index c0cccba6dff4..fdb17c298e17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -19,10 +19,6 @@ package com.android.systemui.communal.data.repository
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
-import android.content.Intent
-import android.content.Intent.ACTION_USER_UNLOCKED
-import android.os.UserHandle
-import android.os.UserManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -38,7 +34,6 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
@@ -68,12 +63,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
- @Mock private lateinit var userManager: UserManager
-
- @Mock private lateinit var userHandle: UserHandle
-
- @Mock private lateinit var userTracker: UserTracker
-
@Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
@Mock private lateinit var providerInfoA: AppWidgetProviderInfo
@@ -86,8 +75,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
private val testScope = kosmos.testScope
- private lateinit var communalRepository: FakeCommunalRepository
-
private lateinit var logBuffer: LogBuffer
private val fakeAllowlist =
@@ -97,68 +84,60 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
"com.android.fake/WidgetProviderC",
)
+ private lateinit var underTest: CommunalWidgetRepositoryImpl
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest")
- communalRepository = kosmos.fakeCommunalRepository
- communalEnabled(true)
setAppWidgetIds(emptyList())
overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
- whenever(userTracker.userHandle).thenReturn(userHandle)
whenever(communalWidgetDao.getWidgets()).thenReturn(flowOf(emptyMap()))
whenever(appWidgetManagerOptional.isPresent).thenReturn(true)
whenever(appWidgetManagerOptional.get()).thenReturn(appWidgetManager)
+
+ underTest =
+ CommunalWidgetRepositoryImpl(
+ appWidgetManagerOptional,
+ appWidgetHost,
+ testScope.backgroundScope,
+ kosmos.testDispatcher,
+ communalWidgetHost,
+ communalWidgetDao,
+ logBuffer,
+ )
}
@Test
- fun neverQueryDbForWidgets_whenFeatureIsDisabled() =
+ fun neverQueryDbForWidgets_whenHostIsInactive() =
testScope.runTest {
- communalEnabled(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
+ underTest.updateAppWidgetHostActive(false)
+ underTest.communalWidgets.launchIn(testScope.backgroundScope)
runCurrent()
verify(communalWidgetDao, never()).getWidgets()
}
@Test
- fun neverQueryDbForWidgets_whenFeatureEnabled_andUserLocked() =
+ fun communalWidgets_whenHostIsActive_queryWidgetsFromDb() =
testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
-
- verify(communalWidgetDao, never()).getWidgets()
- }
+ underTest.updateAppWidgetHostActive(true)
- @Test
- fun communalWidgets_whenUserUnlocked_queryWidgetsFromDb() =
- testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- val communalWidgets by collectLastValue(repository.communalWidgets)
- runCurrent()
val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
whenever(communalWidgetDao.getWidgets())
.thenReturn(flowOf(mapOf(communalItemRankEntry to communalWidgetItemEntry)))
whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
- userUnlocked(true)
installedProviders(listOf(stopwatchProviderInfo))
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(ACTION_USER_UNLOCKED)
- )
- runCurrent()
+ val communalWidgets by collectLastValue(underTest.communalWidgets)
+ runCurrent()
verify(communalWidgetDao).getWidgets()
assertThat(communalWidgets)
.containsExactly(
@@ -173,9 +152,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_allocateId_bindWidget_andAddToDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
@@ -183,7 +160,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
.thenReturn(id)
- repository.addWidget(provider, priority) { true }
+ underTest.addWidget(provider, priority) { true }
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -193,16 +170,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationFails_doNotAddWidgetToDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
- repository.addWidget(provider, priority) { false }
+ underTest.addWidget(provider, priority) { false }
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -213,16 +188,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
- repository.addWidget(provider, priority) { throw IllegalStateException("some error") }
+ underTest.addWidget(provider, priority) { throw IllegalStateException("some error") }
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -233,9 +206,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
@@ -244,7 +215,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
.thenReturn(id)
var configured = false
- repository.addWidget(provider, priority) {
+ underTest.addWidget(provider, priority) {
configured = true
true
}
@@ -258,12 +229,10 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun deleteWidget_removeWidgetId_andDeleteFromDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val id = 1
- repository.deleteWidget(id)
+ underTest.deleteWidget(id)
runCurrent()
verify(communalWidgetDao).deleteWidgetById(id)
@@ -273,108 +242,37 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun reorderWidgets_queryDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
- repository.updateWidgetOrder(widgetIdToPriorityMap)
+ underTest.updateWidgetOrder(widgetIdToPriorityMap)
runCurrent()
verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
}
@Test
- fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
+ fun appWidgetHost_startListening() =
testScope.runTest {
- communalEnabled(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
- assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0)
- }
-
- @Test
- fun broadcastReceiver_featureEnabledAndUserLocked_registerBroadcastReceiver() =
- testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
- assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1)
- }
-
- @Test
- fun appWidgetHost_userUnlocked_startListening() =
- testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
verify(appWidgetHost, never()).startListening()
- userUnlocked(true)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(ACTION_USER_UNLOCKED)
- )
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
verify(appWidgetHost).startListening()
}
@Test
- fun appWidgetHost_userLockedAgain_stopListening() =
+ fun appWidgetHost_stopListening() =
testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
-
- userUnlocked(true)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(ACTION_USER_UNLOCKED)
- )
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
verify(appWidgetHost).startListening()
- verify(appWidgetHost, never()).stopListening()
- userUnlocked(false)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(ACTION_USER_UNLOCKED)
- )
- runCurrent()
+ underTest.updateAppWidgetHostActive(false)
verify(appWidgetHost).stopListening()
}
- private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
- return CommunalWidgetRepositoryImpl(
- appWidgetManagerOptional,
- appWidgetHost,
- testScope.backgroundScope,
- kosmos.testDispatcher,
- fakeBroadcastDispatcher,
- communalRepository,
- communalWidgetHost,
- communalWidgetDao,
- userManager,
- userTracker,
- logBuffer,
- )
- }
-
- private fun communalEnabled(enabled: Boolean) {
- communalRepository.setIsCommunalEnabled(enabled)
- }
-
- private fun userUnlocked(userUnlocked: Boolean) {
- whenever(userManager.isUserUnlockingOrUnlocked(userHandle)).thenReturn(userUnlocked)
- }
-
private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
whenever(appWidgetManager.installedProviders).thenReturn(providers)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
new file mode 100644
index 000000000000..ed29aa4ac202
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.communal.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.communalInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This class is a variation of the [CommunalInteractorTest] for cases where communal is disabled.
+ */
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class CommunalInteractorCommunalDisabledTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var communalRepository: FakeCommunalRepository
+ private lateinit var widgetRepository: FakeCommunalWidgetRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+
+ private lateinit var underTest: CommunalInteractor
+
+ @Before
+ fun setUp() {
+ communalRepository = kosmos.fakeCommunalRepository
+ widgetRepository = kosmos.fakeCommunalWidgetRepository
+ keyguardRepository = kosmos.fakeKeyguardRepository
+
+ communalRepository.setIsCommunalEnabled(false)
+
+ underTest = kosmos.communalInteractor
+ }
+
+ @Test
+ fun isCommunalEnabled_false() =
+ testScope.runTest { assertThat(underTest.isCommunalEnabled).isFalse() }
+
+ @Test
+ fun isCommunalAvailable_whenStorageUnlock_false() =
+ testScope.runTest {
+ val isCommunalAvailable by collectLastValue(underTest.isCommunalAvailable)
+
+ assertThat(isCommunalAvailable).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(isCommunalAvailable).isFalse()
+ }
+
+ @Test
+ fun updateAppWidgetHostActive_whenStorageUnlock_false() =
+ testScope.runTest {
+ assertThat(widgetRepository.isHostActive()).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(widgetRepository.isHostActive()).isFalse()
+ }
+}
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 b62441f72e98..7769223c8af2 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
@@ -62,6 +62,10 @@ import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
+/**
+ * This class of test cases assume that communal is enabled. For disabled cases, see
+ * [CommunalInteractorCommunalDisabledTest].
+ */
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@@ -95,17 +99,43 @@ class CommunalInteractorTest : SysuiTestCase() {
}
@Test
- fun communalEnabled() =
+ fun communalEnabled_true() =
+ testScope.runTest { assertThat(underTest.isCommunalEnabled).isTrue() }
+
+ @Test
+ fun isCommunalAvailable_trueWhenStorageUnlock() =
+ testScope.runTest {
+ val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+ assertThat(isAvailable).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(isAvailable).isTrue()
+ }
+
+ @Test
+ fun isCommunalAvailable_whenStorageUnlock_true() =
testScope.runTest {
- communalRepository.setIsCommunalEnabled(true)
- assertThat(underTest.isCommunalEnabled).isTrue()
+ val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+ assertThat(isAvailable).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(isAvailable).isTrue()
}
@Test
- fun communalDisabled() =
+ fun updateAppWidgetHostActive_uponStorageUnlock_true() =
testScope.runTest {
- communalRepository.setIsCommunalEnabled(false)
- assertThat(underTest.isCommunalEnabled).isFalse()
+ collectLastValue(underTest.isCommunalAvailable)
+ assertThat(widgetRepository.isHostActive()).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(widgetRepository.isHostActive()).isTrue()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index c4ebbdcc2f58..6f62afc560fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -35,6 +35,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -52,6 +53,8 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
@@ -68,6 +71,8 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
+ @Mock private lateinit var userTracker: UserTracker
+ @Captor private lateinit var updateCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
private val mainDispatcher = StandardTestDispatcher()
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -93,6 +98,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
testScope.backgroundScope,
systemClock,
facePropertyRepository,
+ userTracker,
)
}
@@ -553,4 +559,25 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
+
+ @Test
+ fun isEncryptedOrLockdown() =
+ testScope.runTest {
+ whenever(userTracker.userId).thenReturn(0)
+ whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(0)).thenReturn(true)
+
+ // Default value for isEncryptedOrLockdown is true
+ val isEncryptedOrLockdown by collectLastValue(underTest.isEncryptedOrLockdown)
+ assertThat(isEncryptedOrLockdown).isTrue()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateCallbackCaptor.capture())
+ val updateCallback = updateCallbackCaptor.value
+
+ // Strong auth state updated
+ whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(0)).thenReturn(false)
+ updateCallback.onStrongAuthStateChanged(0)
+
+ // Verify no longer encrypted or lockdown
+ assertThat(isEncryptedOrLockdown).isFalse()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index bfc5019801d0..1c362e993509 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -18,11 +18,7 @@ package com.android.systemui.communal.data.repository
import android.appwidget.AppWidgetManager
import android.content.ComponentName
-import android.content.Intent
-import android.content.IntentFilter
-import android.os.UserManager
import androidx.annotation.WorkerThread
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.db.CommunalItemRank
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
@@ -35,7 +31,6 @@ 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.settings.UserTracker
import java.util.Optional
import javax.inject.Inject
import kotlin.coroutines.cancellation.CancellationException
@@ -43,16 +38,12 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
/** Encapsulates the state of widgets for communal mode. */
interface CommunalWidgetRepository {
@@ -75,9 +66,11 @@ interface CommunalWidgetRepository {
* @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
*/
fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
+
+ /** Update whether the app widget host should be active. */
+ fun updateAppWidgetHostActive(active: Boolean)
}
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalWidgetRepositoryImpl
@Inject
@@ -86,12 +79,8 @@ constructor(
private val appWidgetHost: CommunalAppWidgetHost,
@Application private val applicationScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
- broadcastDispatcher: BroadcastDispatcher,
- communalRepository: CommunalRepository,
private val communalWidgetHost: CommunalWidgetHost,
private val communalWidgetDao: CommunalWidgetDao,
- private val userManager: UserManager,
- private val userTracker: UserTracker,
@CommunalLog logBuffer: LogBuffer,
) : CommunalWidgetRepository {
companion object {
@@ -100,40 +89,22 @@ constructor(
private val logger = Logger(logBuffer, TAG)
- // Whether the [AppWidgetHost] is listening for updates.
- private var isHostListening = false
-
- private suspend fun isUserUnlockingOrUnlocked(): Boolean =
- withContext(bgDispatcher) { userManager.isUserUnlockingOrUnlocked(userTracker.userHandle) }
-
- private val isUserUnlocked: Flow<Boolean> =
- flowOf(communalRepository.isCommunalEnabled)
- .flatMapLatest { enabled ->
- if (enabled) {
- broadcastDispatcher
- .broadcastFlow(
- filter = IntentFilter(Intent.ACTION_USER_UNLOCKED),
- user = userTracker.userHandle
- )
- .onStart { emit(Unit) }
- .mapLatest { isUserUnlockingOrUnlocked() }
- } else {
- emptyFlow()
- }
- }
- .distinctUntilChanged()
-
- private val isHostActive: Flow<Boolean> =
- isUserUnlocked.map {
- if (it) {
- startListening()
- true
- } else {
- stopListening()
- false
- }
+ override fun updateAppWidgetHostActive(active: Boolean) {
+ if (active == isHostActive.value) {
+ return
+ }
+
+ if (active) {
+ appWidgetHost.startListening()
+ } else {
+ appWidgetHost.stopListening()
}
+ isHostActive.value = active
+ }
+
+ private val isHostActive = MutableStateFlow(false)
+ @OptIn(ExperimentalCoroutinesApi::class)
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
isHostActive.flatMapLatest { isHostActive ->
if (!isHostActive || !appWidgetManager.isPresent) {
@@ -218,22 +189,4 @@ constructor(
priority = entry.key.rank,
)
}
-
- private fun startListening() {
- if (isHostListening) {
- return
- }
-
- appWidgetHost.startListening()
- isHostListening = true
- }
-
- private fun stopListening() {
- if (!isHostListening) {
- return
- }
-
- appWidgetHost.stopListening()
- isHostListening = false
- }
}
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 aa4a9d0d9569..71523b9e750f 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
@@ -32,22 +32,30 @@ import com.android.systemui.communal.shared.model.ObservableCommunalTransitionSt
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
/** Encapsulates business-logic related to communal mode. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalInteractor
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
private val communalPrefsRepository: CommunalPrefsRepository,
@@ -62,6 +70,20 @@ constructor(
val isCommunalEnabled: Boolean
get() = communalRepository.isCommunalEnabled
+ /** Whether communal features are enabled and available. */
+ val isCommunalAvailable: StateFlow<Boolean> =
+ flowOf(isCommunalEnabled)
+ .flatMapLatest { enabled ->
+ if (enabled) keyguardInteractor.isEncryptedOrLockdown.map { !it } else flowOf(false)
+ }
+ .distinctUntilChanged()
+ .onEach { available -> widgetRepository.updateAppWidgetHostActive(available) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through
* [onSceneChanged].
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 4da348e6a92a..73bb0b0ec8a3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -33,6 +33,8 @@ abstract class BaseCommunalViewModel(
private val communalInteractor: CommunalInteractor,
val mediaHost: MediaHost,
) {
+ val isCommunalAvailable: StateFlow<Boolean> = communalInteractor.isCommunalAvailable
+
val isKeyguardVisible: Flow<Boolean> = communalInteractor.isKeyguardVisible
val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 704ebdd40af6..d012d24b767e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -40,11 +40,13 @@ import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -54,7 +56,10 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
/** Defines interface for classes that encapsulate application state for the keyguard. */
@@ -208,6 +213,12 @@ interface KeyguardRepository {
val clockShouldBeCentered: Flow<Boolean>
/**
+ * Whether the primary authentication is required for the given user due to lockdown or
+ * encryption after reboot.
+ */
+ val isEncryptedOrLockdown: Flow<Boolean>
+
+ /**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
* Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
@@ -279,6 +290,7 @@ constructor(
@Application private val scope: CoroutineScope,
private val systemClock: SystemClock,
facePropertyRepository: FacePropertyRepository,
+ private val userTracker: UserTracker,
) : KeyguardRepository {
private val _dismissAction: MutableStateFlow<DismissAction> =
MutableStateFlow(DismissAction.None)
@@ -544,6 +556,24 @@ constructor(
awaitClose { dozeTransitionListener.removeCallback(callback) }
}
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override val isEncryptedOrLockdown: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onStrongAuthStateChanged(userId: Int) {
+ trySend(userId)
+ }
+ }
+
+ keyguardUpdateMonitor.registerCallback(callback)
+
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+ .filter { userId -> userId == userTracker.userId }
+ .onStart { emit(userTracker.userId) }
+ .mapLatest { userId -> keyguardUpdateMonitor.isEncryptedOrLockdown(userId) }
+
override fun isKeyguardShowing(): Boolean {
return keyguardStateController.isShowing
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6eb3b64d4c09..6170356d4a90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -264,6 +264,12 @@ constructor(
}
}
+ /**
+ * Whether the primary authentication is required for the given user due to lockdown or
+ * encryption after reboot.
+ */
+ val isEncryptedOrLockdown: Flow<Boolean> = repository.isEncryptedOrLockdown
+
fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
return dozeTransitionModel.filter { states.contains(it.to) }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 397dc1a464bd..d0c2d4b82fb3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -35,4 +35,13 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
}
}
}
+
+ private var isHostActive = false
+ override fun updateAppWidgetHostActive(active: Boolean) {
+ isHostActive = active
+ }
+
+ fun isHostActive(): Boolean {
+ return isHostActive
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
index 126bd6f03cca..1753ca05347a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
@@ -67,6 +67,7 @@ object CommunalInteractorFactory {
appWidgetHost,
editWidgetsActivityStarter,
CommunalInteractor(
+ testScope.backgroundScope,
communalRepository,
widgetRepository,
communalPrefsRepository,
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 7cbbaabe9dfa..65579a6d9ddf 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
@@ -23,11 +23,13 @@ import com.android.systemui.communal.data.repository.communalWidgetRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.util.mockito.mock
val Kosmos.communalInteractor by Fixture {
CommunalInteractor(
+ applicationScope = applicationCoroutineScope,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
mediaRepository = communalMediaRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 975db3b179ac..59f56dd18f0e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -127,6 +127,9 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ private val _isEncryptedOrLockdown = MutableStateFlow(true)
+ override val isEncryptedOrLockdown: Flow<Boolean> = _isEncryptedOrLockdown
+
override fun setQuickSettingsVisible(isVisible: Boolean) {
_isQuickSettingsVisible.value = isVisible
}
@@ -247,6 +250,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
override fun setKeyguardAlpha(alpha: Float) {
_keyguardAlpha.value = alpha
}
+
+ fun setIsEncryptedOrLockdown(value: Boolean) {
+ _isEncryptedOrLockdown.value = value
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
index 565ce2b41521..d8f0cec54596 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
@@ -24,12 +24,14 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import org.mockito.Mockito.mock
val Kosmos.communalInteractor by
Kosmos.Fixture {
CommunalInteractor(
+ applicationScope = testScope.backgroundScope,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
mediaRepository = communalMediaRepository,