diff options
15 files changed, 360 insertions, 242 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt new file mode 100644 index 000000000000..4deb3547b522 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryImplTest.kt @@ -0,0 +1,120 @@ +/* + * 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.data.repository + +import android.app.smartspace.SmartspaceTarget +import android.app.smartspace.flags.Flags.FLAG_REMOTE_VIEWS +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.smartspace.CommunalSmartspaceController +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener +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 +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class CommunalSmartspaceRepositoryImplTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val listenerCaptor = argumentCaptor<SmartspaceTargetListener>() + + private val smartspaceController = mock<CommunalSmartspaceController>() + private val fakeExecutor = kosmos.fakeExecutor + + private lateinit var underTest: CommunalSmartspaceRepositoryImpl + + @Before + fun setUp() { + underTest = CommunalSmartspaceRepositoryImpl(smartspaceController, fakeExecutor) + } + + @DisableFlags(FLAG_REMOTE_VIEWS) + @Test + fun communalTimers_doNotListenForSmartspaceUpdatesWhenRemoteViewsFlagDisabled() = + testScope.runTest { + collectLastValue(underTest.timers) + runCurrent() + fakeExecutor.runAllReady() + + verify(smartspaceController, never()).addListener(any()) + } + + @EnableFlags(FLAG_REMOTE_VIEWS) + @Test + fun communalTimers_onlyShowTimersWithRemoteViews() = + testScope.runTest { + val communalTimers by collectLastValue(underTest.timers) + runCurrent() + fakeExecutor.runAllReady() + + with(captureSmartspaceTargetListener()) { + onSmartspaceTargetsUpdated( + listOf( + // Invalid. Not a timer + mock<SmartspaceTarget> { + on { smartspaceTargetId }.doReturn("weather") + on { featureType }.doReturn(SmartspaceTarget.FEATURE_WEATHER) + }, + // Invalid. RemoteViews absent + mock<SmartspaceTarget> { + on { smartspaceTargetId }.doReturn("timer-0-started") + on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER) + on { remoteViews }.doReturn(null) + on { creationTimeMillis }.doReturn(1000) + }, + // Valid + mock<SmartspaceTarget> { + on { smartspaceTargetId }.doReturn("timer-1-started") + on { featureType }.doReturn(SmartspaceTarget.FEATURE_TIMER) + on { remoteViews }.doReturn(mock()) + on { creationTimeMillis }.doReturn(2000) + }, + ) + ) + } + runCurrent() + + // Verify that only the valid target is listed + assertThat(communalTimers?.size).isEqualTo(1) + assertThat(communalTimers?.first()?.smartspaceTargetId).isEqualTo("timer-1-started") + } + + private fun captureSmartspaceTargetListener(): SmartspaceTargetListener { + verify(smartspaceController).addListener(listenerCaptor.capture()) + return listenerCaptor.firstValue + } +} 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 7b26db50814e..5cdbe9ce5856 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 @@ -19,7 +19,6 @@ package com.android.systemui.communal.domain.interactor import android.app.admin.DevicePolicyManager import android.app.admin.devicePolicyManager -import android.app.smartspace.SmartspaceTarget import android.appwidget.AppWidgetProviderInfo import android.content.Intent import android.content.pm.UserInfo @@ -36,14 +35,17 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.communal.data.model.CommunalSmartspaceTimer import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository +import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository +import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel @@ -69,8 +71,6 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.fakeUserTracker -import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository -import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository @@ -114,7 +114,7 @@ class CommunalInteractorTest : SysuiTestCase() { private lateinit var communalRepository: FakeCommunalSceneRepository private lateinit var mediaRepository: FakeCommunalMediaRepository private lateinit var widgetRepository: FakeCommunalWidgetRepository - private lateinit var smartspaceRepository: FakeSmartspaceRepository + private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository private lateinit var userRepository: FakeUserRepository private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository @@ -135,7 +135,7 @@ class CommunalInteractorTest : SysuiTestCase() { communalRepository = kosmos.fakeCommunalSceneRepository mediaRepository = kosmos.fakeCommunalMediaRepository widgetRepository = kosmos.fakeCommunalWidgetRepository - smartspaceRepository = kosmos.fakeSmartspaceRepository + smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository userRepository = kosmos.fakeUserRepository keyguardRepository = kosmos.fakeKeyguardRepository editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter @@ -265,44 +265,6 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun smartspace_onlyShowTimersWithRemoteViews() = - testScope.runTest { - // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - - // Not a timer - val target1 = mock(SmartspaceTarget::class.java) - whenever(target1.smartspaceTargetId).thenReturn("target1") - whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_WEATHER) - whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java)) - whenever(target1.creationTimeMillis).thenReturn(0L) - - // Does not have RemoteViews - val target2 = mock(SmartspaceTarget::class.java) - whenever(target2.smartspaceTargetId).thenReturn("target2") - whenever(target2.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) - whenever(target2.remoteViews).thenReturn(null) - whenever(target2.creationTimeMillis).thenReturn(0L) - - // Timer and has RemoteViews - val target3 = mock(SmartspaceTarget::class.java) - whenever(target3.smartspaceTargetId).thenReturn("target3") - whenever(target3.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) - whenever(target3.remoteViews).thenReturn(mock(RemoteViews::class.java)) - whenever(target3.creationTimeMillis).thenReturn(0L) - - val targets = listOf(target1, target2, target3) - smartspaceRepository.setCommunalSmartspaceTargets(targets) - - val smartspaceContent by collectLastValue(underTest.getOngoingContent(true)) - assertThat(smartspaceContent?.size).isEqualTo(1) - assertThat(smartspaceContent?.get(0)?.key) - .isEqualTo(CommunalContentModel.KEY.smartspace("target3")) - } - - @Test fun smartspaceDynamicSizing_oneCard_fullSize() = testSmartspaceDynamicSizing( totalTargets = 1, @@ -387,12 +349,12 @@ class CommunalInteractorTest : SysuiTestCase() { keyguardRepository.setKeyguardOccluded(false) tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - val targets = mutableListOf<SmartspaceTarget>() + val targets = mutableListOf<CommunalSmartspaceTimer>() for (index in 0 until totalTargets) { targets.add(smartspaceTimer(index.toString())) } - smartspaceRepository.setCommunalSmartspaceTargets(targets) + smartspaceRepository.setTimers(targets) val smartspaceContent by collectLastValue(underTest.getOngoingContent(true)) assertThat(smartspaceContent?.size).isEqualTo(totalTargets) @@ -441,18 +403,18 @@ class CommunalInteractorTest : SysuiTestCase() { // Timer1 started val timer1 = smartspaceTimer("timer1", timestamp = 1L) - smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1)) + smartspaceRepository.setTimers(listOf(timer1)) // Umo started mediaRepository.mediaActive(timestamp = 2L) // Timer2 started val timer2 = smartspaceTimer("timer2", timestamp = 3L) - smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2)) + smartspaceRepository.setTimers(listOf(timer1, timer2)) // Timer3 started val timer3 = smartspaceTimer("timer3", timestamp = 4L) - smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2, timer3)) + smartspaceRepository.setTimers(listOf(timer1, timer2, timer3)) val ongoingContent by collectLastValue(underTest.getOngoingContent(true)) assertThat(ongoingContent?.size).isEqualTo(4) @@ -1089,13 +1051,12 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(showCommunalFromOccluded).isTrue() } - private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget { - val timer = mock(SmartspaceTarget::class.java) - whenever(timer.smartspaceTargetId).thenReturn(id) - whenever(timer.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) - whenever(timer.remoteViews).thenReturn(mock(RemoteViews::class.java)) - whenever(timer.creationTimeMillis).thenReturn(timestamp) - return timer + private fun smartspaceTimer(id: String, timestamp: Long = 0L): CommunalSmartspaceTimer { + return CommunalSmartspaceTimer( + smartspaceTargetId = id, + createdTimestampMillis = timestamp, + remoteViews = mock(RemoteViews::class.java) + ) } private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index 0190ccba3caa..a2f6796c6a38 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.communal.view.viewmodel -import android.app.smartspace.SmartspaceTarget import android.appwidget.AppWidgetProviderInfo import android.content.ActivityNotFoundException import android.content.Intent @@ -32,10 +31,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.model.CommunalSmartspaceTimer import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor @@ -57,8 +59,6 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.settings.fakeUserTracker -import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository -import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.any @@ -91,7 +91,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { private lateinit var tutorialRepository: FakeCommunalTutorialRepository private lateinit var widgetRepository: FakeCommunalWidgetRepository - private lateinit var smartspaceRepository: FakeSmartspaceRepository + private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository private lateinit var mediaRepository: FakeCommunalMediaRepository private lateinit var communalSceneInteractor: CommunalSceneInteractor @@ -105,7 +105,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { tutorialRepository = kosmos.fakeCommunalTutorialRepository widgetRepository = kosmos.fakeCommunalWidgetRepository - smartspaceRepository = kosmos.fakeSmartspaceRepository + smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository mediaRepository = kosmos.fakeCommunalMediaRepository communalSceneInteractor = kosmos.communalSceneInteractor kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO)) @@ -152,11 +152,15 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { widgetRepository.setCommunalWidgets(widgets) // Smartspace available. - val target = Mockito.mock(SmartspaceTarget::class.java) - whenever(target.smartspaceTargetId).thenReturn("target") - whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) - whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java)) - smartspaceRepository.setCommunalSmartspaceTargets(listOf(target)) + smartspaceRepository.setTimers( + listOf( + CommunalSmartspaceTimer( + smartspaceTargetId = "target", + createdTimestampMillis = 0L, + remoteViews = Mockito.mock(RemoteViews::class.java), + ) + ) + ) // Media playing. mediaRepository.mediaActive() 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 d33877462ec6..74a048d038bd 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 @@ -16,7 +16,6 @@ package com.android.systemui.communal.view.viewmodel -import android.app.smartspace.SmartspaceTarget import android.appwidget.AppWidgetProviderInfo import android.content.pm.UserInfo import android.os.UserHandle @@ -27,12 +26,15 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.model.CommunalSmartspaceTimer import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository +import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository +import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.communalInteractor @@ -78,8 +80,6 @@ import com.android.systemui.settings.fakeUserTracker import com.android.systemui.shade.ShadeTestUtil import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.shadeTestUtil -import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository -import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository @@ -115,7 +115,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var tutorialRepository: FakeCommunalTutorialRepository private lateinit var widgetRepository: FakeCommunalWidgetRepository - private lateinit var smartspaceRepository: FakeSmartspaceRepository + private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository private lateinit var mediaRepository: FakeCommunalMediaRepository private lateinit var userRepository: FakeUserRepository private lateinit var shadeTestUtil: ShadeTestUtil @@ -136,7 +136,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository tutorialRepository = kosmos.fakeCommunalTutorialRepository widgetRepository = kosmos.fakeCommunalWidgetRepository - smartspaceRepository = kosmos.fakeSmartspaceRepository + smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository mediaRepository = kosmos.fakeCommunalMediaRepository userRepository = kosmos.fakeUserRepository shadeTestUtil = kosmos.shadeTestUtil @@ -222,11 +222,15 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { widgetRepository.setCommunalWidgets(widgets) // Smartspace available. - val target = Mockito.mock(SmartspaceTarget::class.java) - whenever(target.smartspaceTargetId).thenReturn("target") - whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) - whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java)) - smartspaceRepository.setCommunalSmartspaceTargets(listOf(target)) + smartspaceRepository.setTimers( + listOf( + CommunalSmartspaceTimer( + smartspaceTargetId = "target", + createdTimestampMillis = 0L, + remoteViews = Mockito.mock(RemoteViews::class.java), + ) + ) + ) // Media playing. mediaRepository.mediaActive() @@ -293,7 +297,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { widgetRepository.setCommunalWidgets(emptyList()) // UMO playing mediaRepository.mediaActive() - smartspaceRepository.setCommunalSmartspaceTargets(emptyList()) + smartspaceRepository.setTimers(emptyList()) val isEmptyState by collectLastValue(underTest.isEmptyState) assertThat(isEmptyState).isTrue() @@ -314,7 +318,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { ), ) mediaRepository.mediaInactive() - smartspaceRepository.setCommunalSmartspaceTargets(emptyList()) + smartspaceRepository.setTimers(emptyList()) val isEmptyState by collectLastValue(underTest.isEmptyState) assertThat(isEmptyState).isFalse() @@ -689,11 +693,15 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { advanceTimeBy(60L) // New timer available - val target = Mockito.mock(SmartspaceTarget::class.java) - whenever<String?>(target.smartspaceTargetId).thenReturn("target") - whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) - whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java)) - smartspaceRepository.setCommunalSmartspaceTargets(listOf(target)) + smartspaceRepository.setTimers( + listOf( + CommunalSmartspaceTimer( + smartspaceTargetId = "target", + createdTimestampMillis = 0L, + remoteViews = Mockito.mock(RemoteViews::class.java), + ) + ) + ) runCurrent() // Still only emits widgets and the CTA tile @@ -748,11 +756,15 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { assertThat(communalContent).hasSize(3) // When new timer available - val target = Mockito.mock(SmartspaceTarget::class.java) - whenever(target.smartspaceTargetId).thenReturn("target") - whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) - whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java)) - smartspaceRepository.setCommunalSmartspaceTargets(listOf(target)) + smartspaceRepository.setTimers( + listOf( + CommunalSmartspaceTimer( + smartspaceTargetId = "target", + createdTimestampMillis = 0L, + remoteViews = Mockito.mock(RemoteViews::class.java), + ) + ) + ) runCurrent() // Then emits timer, widgets and the CTA tile diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt index 2406cc6bcea2..a78f7ce4684a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -23,6 +23,7 @@ import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModu import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryModule import com.android.systemui.communal.data.repository.CommunalRepositoryModule import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule +import com.android.systemui.communal.data.repository.CommunalSmartspaceRepositoryModule import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule import com.android.systemui.communal.shared.model.CommunalScenes @@ -52,6 +53,7 @@ import kotlinx.coroutines.CoroutineScope CommunalWidgetModule::class, CommunalPrefsRepositoryModule::class, CommunalSettingsRepositoryModule::class, + CommunalSmartspaceRepositoryModule::class, ] ) interface CommunalModule { diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalSmartspaceTimer.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalSmartspaceTimer.kt new file mode 100644 index 000000000000..ff9dddd7c516 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalSmartspaceTimer.kt @@ -0,0 +1,29 @@ +/* + * 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.data.model + +import android.widget.RemoteViews + +/** Data model of a smartspace timer in the Glanceable Hub. */ +data class CommunalSmartspaceTimer( + /** Unique id that identifies the timer. */ + val smartspaceTargetId: String, + /** Timestamp in milliseconds of when the timer was created. */ + val createdTimestampMillis: Long, + /** Remote views for the timer that is rendered in Glanceable Hub. */ + val remoteViews: RemoteViews, +) diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt new file mode 100644 index 000000000000..a9eafb65fb92 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt @@ -0,0 +1,85 @@ +/* + * 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.data.repository + +import android.app.smartspace.SmartspaceTarget +import android.os.Parcelable +import com.android.systemui.communal.data.model.CommunalSmartspaceTimer +import com.android.systemui.communal.smartspace.CommunalSmartspaceController +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.BcSmartspaceDataPlugin +import java.util.concurrent.Executor +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onStart + +interface CommunalSmartspaceRepository { + /** Smartspace timer targets for the communal surface. */ + val timers: Flow<List<CommunalSmartspaceTimer>> +} + +@SysUISingleton +class CommunalSmartspaceRepositoryImpl +@Inject +constructor( + private val communalSmartspaceController: CommunalSmartspaceController, + @Main private val uiExecutor: Executor, +) : CommunalSmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener { + + private val _timers: MutableStateFlow<List<CommunalSmartspaceTimer>> = + MutableStateFlow(emptyList()) + override val timers: Flow<List<CommunalSmartspaceTimer>> = + if (!android.app.smartspace.flags.Flags.remoteViews()) emptyFlow() + else + _timers + .onStart { + uiExecutor.execute { + communalSmartspaceController.addListener( + listener = this@CommunalSmartspaceRepositoryImpl + ) + } + } + .onCompletion { + uiExecutor.execute { + communalSmartspaceController.removeListener( + listener = this@CommunalSmartspaceRepositoryImpl + ) + } + } + + override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) { + val targets = targetsNullable?.filterIsInstance<SmartspaceTarget>() ?: emptyList() + + _timers.value = + targets + .filter { target -> + target.featureType == SmartspaceTarget.FEATURE_TIMER && + target.remoteViews != null + } + .map { target -> + CommunalSmartspaceTimer( + smartspaceTargetId = target.smartspaceTargetId, + createdTimestampMillis = target.creationTimeMillis, + remoteViews = target.remoteViews!!, + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryModule.kt index c77bcc50b69a..b11c6d6d5d55 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryModule.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -14,12 +14,15 @@ * limitations under the License. */ -package com.android.systemui.smartspace.data.repository +package com.android.systemui.communal.data.repository import dagger.Binds import dagger.Module @Module -interface SmartspaceRepositoryModule { - @Binds fun smartspaceRepository(impl: SmartspaceRepositoryImpl): SmartspaceRepository +interface CommunalSmartspaceRepositoryModule { + @Binds + fun communalSmartspaceRepository( + impl: CommunalSmartspaceRepositoryImpl + ): CommunalSmartspaceRepository } 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 f5255ac4d545..77aab3caf3b6 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 @@ -16,7 +16,6 @@ package com.android.systemui.communal.domain.interactor -import android.app.smartspace.SmartspaceTarget import android.content.ComponentName import android.content.Intent import android.content.IntentFilter @@ -28,6 +27,7 @@ import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.communal.data.repository.CommunalMediaRepository +import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent @@ -59,7 +59,6 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.UserTracker -import com.android.systemui.smartspace.data.repository.SmartspaceRepository import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not import com.android.systemui.util.kotlin.emitOnStart @@ -81,7 +80,6 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -100,7 +98,7 @@ constructor( private val widgetRepository: CommunalWidgetRepository, private val communalPrefsInteractor: CommunalPrefsInteractor, private val mediaRepository: CommunalMediaRepository, - smartspaceRepository: SmartspaceRepository, + private val smartspaceRepository: CommunalSmartspaceRepository, keyguardInteractor: KeyguardInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, communalSettingsInteractor: CommunalSettingsInteractor, @@ -436,19 +434,6 @@ constructor( } } - /** A flow of available smartspace targets. Currently only showing timers. */ - private val smartspaceTargets: Flow<List<SmartspaceTarget>> = - if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) { - flowOf(emptyList()) - } else { - smartspaceRepository.communalSmartspaceTargets.map { targets -> - targets.filter { target -> - target.featureType == SmartspaceTarget.FEATURE_TIMER && - target.remoteViews != null - } - } - } - /** CTA tile to be displayed in the glanceable hub (view mode). */ val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> = communalPrefsInteractor.isCtaDismissed.map { isDismissed -> @@ -473,16 +458,16 @@ constructor( * sized dynamically. */ fun getOngoingContent(mediaHostVisible: Boolean): Flow<List<CommunalContentModel.Ongoing>> = - combine(smartspaceTargets, mediaRepository.mediaModel) { smartspace, media -> + combine(smartspaceRepository.timers, mediaRepository.mediaModel) { timers, media -> val ongoingContent = mutableListOf<CommunalContentModel.Ongoing>() - // Add smartspace + // Add smartspace timers ongoingContent.addAll( - smartspace.map { target -> + timers.map { timer -> CommunalContentModel.Smartspace( - smartspaceTargetId = target.smartspaceTargetId, - remoteViews = target.remoteViews!!, - createdTimestampMillis = target.creationTimeMillis, + smartspaceTargetId = timer.smartspaceTargetId, + remoteViews = timer.remoteViews, + createdTimestampMillis = timer.createdTimestampMillis, ) } ) diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt index 2f58b35ae182..ea4e065be84b 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt @@ -18,51 +18,35 @@ package com.android.systemui.smartspace.dagger import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.smartspace.SmartspacePrecondition import com.android.systemui.smartspace.SmartspaceTargetFilter -import com.android.systemui.smartspace.data.repository.SmartspaceRepositoryModule import com.android.systemui.smartspace.preconditions.LockscreenPrecondition import dagger.Binds import dagger.BindsOptionalOf import dagger.Module import javax.inject.Named -@Module(subcomponents = [SmartspaceViewComponent::class], - includes = [SmartspaceRepositoryModule::class]) +@Module(subcomponents = [SmartspaceViewComponent::class]) abstract class SmartspaceModule { @Module companion object { - /** - * The BcSmartspaceDataProvider for dreams. - */ + /** The BcSmartspaceDataProvider for dreams. */ const val DREAM_SMARTSPACE_DATA_PLUGIN = "dreams_smartspace_data_plugin" - /** - * The BcSmartspaceDataPlugin for the standalone weather on dream. - */ + /** The BcSmartspaceDataPlugin for the standalone weather on dream. */ const val DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN = "dream_weather_smartspace_data_plugin" - /** - * The target filter for smartspace over lockscreen. - */ + /** The target filter for smartspace over lockscreen. */ const val LOCKSCREEN_SMARTSPACE_TARGET_FILTER = "lockscreen_smartspace_target_filter" - /** - * The precondition for smartspace over lockscreen - */ + /** The precondition for smartspace over lockscreen */ const val LOCKSCREEN_SMARTSPACE_PRECONDITION = "lockscreen_smartspace_precondition" - /** - * The BcSmartspaceDataPlugin for the standalone date (+alarm+dnd). - */ + /** The BcSmartspaceDataPlugin for the standalone date (+alarm+dnd). */ const val DATE_SMARTSPACE_DATA_PLUGIN = "date_smartspace_data_plugin" - /** - * The BcSmartspaceDataPlugin for the standalone weather. - */ + /** The BcSmartspaceDataPlugin for the standalone weather. */ const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin" - /** - * The BcSmartspaceDataProvider for the glanceable hub. - */ + /** The BcSmartspaceDataProvider for the glanceable hub. */ const val GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN = "glanceable_hub_smartspace_data_plugin" } diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt deleted file mode 100644 index 52a1c1555591..000000000000 --- a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2023 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.smartspace.data.repository - -import android.app.smartspace.SmartspaceTarget -import android.os.Parcelable -import android.widget.RemoteViews -import com.android.systemui.communal.smartspace.CommunalSmartspaceController -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.plugins.BcSmartspaceDataPlugin -import java.util.concurrent.Executor -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.flow.onStart - -interface SmartspaceRepository { - /** Whether [RemoteViews] are passed through smartspace targets. */ - val isSmartspaceRemoteViewsEnabled: Boolean - - /** Smartspace targets for the communal surface. */ - val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> -} - -@SysUISingleton -class SmartspaceRepositoryImpl -@Inject -constructor( - private val communalSmartspaceController: CommunalSmartspaceController, - @Main private val uiExecutor: Executor, -) : SmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener { - - override val isSmartspaceRemoteViewsEnabled: Boolean - get() = android.app.smartspace.flags.Flags.remoteViews() - - private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> = - MutableStateFlow(emptyList()) - override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> = - _communalSmartspaceTargets - .onStart { - uiExecutor.execute { - communalSmartspaceController.addListener( - listener = this@SmartspaceRepositoryImpl - ) - } - } - .onCompletion { - uiExecutor.execute { - communalSmartspaceController.removeListener( - listener = this@SmartspaceRepositoryImpl - ) - } - } - - override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) { - targetsNullable?.let { targets -> - _communalSmartspaceTargets.value = targets.filterIsInstance<SmartspaceTarget>() - } - ?: run { _communalSmartspaceTargets.value = emptyList() } - } -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryKosmos.kt index 0e4c923a3078..559a6eeb814a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/SmartspaceRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepositoryKosmos.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.android.systemui.smartspace.data.repository +package com.android.systemui.communal.data.repository import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.Kosmos.Fixture -val Kosmos.fakeSmartspaceRepository by Fixture { FakeSmartspaceRepository() } +val Kosmos.fakeCommunalSmartspaceRepository by Kosmos.Fixture { FakeCommunalSmartspaceRepository() } -val Kosmos.smartspaceRepository by Fixture<SmartspaceRepository> { fakeSmartspaceRepository } +val Kosmos.communalSmartspaceRepository by + Kosmos.Fixture<CommunalSmartspaceRepository> { fakeCommunalSmartspaceRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSmartspaceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSmartspaceRepository.kt new file mode 100644 index 000000000000..7bbc6f0ba6bc --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSmartspaceRepository.kt @@ -0,0 +1,31 @@ +/* + * 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.data.repository + +import com.android.systemui.communal.data.model.CommunalSmartspaceTimer +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeCommunalSmartspaceRepository : CommunalSmartspaceRepository { + + private val _timers = MutableStateFlow<List<CommunalSmartspaceTimer>>(emptyList()) + override val timers: Flow<List<CommunalSmartspaceTimer>> = _timers + + fun setTimers(timers: List<CommunalSmartspaceTimer>) { + _timers.value = timers + } +} 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 b58861b1104e..d6d36f706c62 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 @@ -19,6 +19,7 @@ package com.android.systemui.communal.domain.interactor import android.os.userManager import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.communal.data.repository.communalMediaRepository +import com.android.systemui.communal.data.repository.communalSmartspaceRepository import com.android.systemui.communal.data.repository.communalWidgetRepository import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.flags.Flags @@ -34,7 +35,6 @@ import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.activityStarter import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.settings.userTracker -import com.android.systemui.smartspace.data.repository.smartspaceRepository import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.mock @@ -47,7 +47,7 @@ val Kosmos.communalInteractor by Fixture { widgetRepository = communalWidgetRepository, communalPrefsInteractor = communalPrefsInteractor, mediaRepository = communalMediaRepository, - smartspaceRepository = smartspaceRepository, + smartspaceRepository = communalSmartspaceRepository, keyguardInteractor = keyguardInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, communalSettingsInteractor = communalSettingsInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt deleted file mode 100644 index 862e52d7703f..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.android.systemui.smartspace.data.repository - -import android.app.smartspace.SmartspaceTarget -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow - -class FakeSmartspaceRepository( - smartspaceRemoteViewsEnabled: Boolean = true, -) : SmartspaceRepository { - - override val isSmartspaceRemoteViewsEnabled = smartspaceRemoteViewsEnabled - - private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> = - MutableStateFlow(emptyList()) - override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> = - _communalSmartspaceTargets - - fun setCommunalSmartspaceTargets(targets: List<SmartspaceTarget>) { - _communalSmartspaceTargets.value = targets - } -} |