diff options
| author | 2023-10-31 16:37:00 -0700 | |
|---|---|---|
| committer | 2023-11-02 10:51:45 -0700 | |
| commit | 60c985fc53b353d53cae205b5953fe9eaefb8937 (patch) | |
| tree | c8e024618f2dc0b23944557bbc803e29bd3814e3 | |
| parent | 1c93bfcc4ee3300348bfa0b8caddf6d08598cd7a (diff) | |
Add new communal location for UMO
The communal hub UI is accessed from an edge swipe from either the
lock screen or from on top of the dream. We intend for the UMO to be
visible there if media is playing or has played recently.
Bug: 308638964
Bug: 304584416
Flag: ACONFIG com.android.systemui.communal_hub DEVELOPMENT
Test: atest MediaHierarchyManagerTest
Change-Id: If68dee0d3b56df43e53d7066f9aed1ed2c59afb9
11 files changed, 260 insertions, 18 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 46d418a03c00..37804682ed98 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 @@ -14,6 +14,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -29,12 +30,9 @@ import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.transitions +import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.ui.viewmodel.CommunalViewModel - -object Scenes { - val Blank = SceneKey(name = "blank") - val Communal = SceneKey(name = "communal") -} +import kotlinx.coroutines.flow.transform object Communal { object Elements { @@ -43,7 +41,7 @@ object Communal { } val sceneTransitions = transitions { - from(Scenes.Blank, to = Scenes.Communal) { + from(TransitionSceneKey.Blank, to = TransitionSceneKey.Communal) { spec = tween(durationMillis = 500) translate(Communal.Elements.Content, Edge.Right) @@ -58,8 +56,14 @@ val sceneTransitions = transitions { * handling and transitions before the full Flexiglass layout is ready. */ @Composable -fun CommunalContainer(modifier: Modifier = Modifier, viewModel: CommunalViewModel) { - val (currentScene, setCurrentScene) = remember { mutableStateOf(Scenes.Blank) } +fun CommunalContainer( + modifier: Modifier = Modifier, + viewModel: CommunalViewModel, +) { + val currentScene: SceneKey by + viewModel.currentScene + .transform<CommunalSceneKey, SceneKey> { value -> value.toTransitionSceneKey() } + .collectAsState(TransitionSceneKey.Blank) // Failsafe to hide the whole SceneTransitionLayout in case of bugginess. var showSceneTransitionLayout by remember { mutableStateOf(true) } @@ -70,16 +74,19 @@ fun CommunalContainer(modifier: Modifier = Modifier, viewModel: CommunalViewMode SceneTransitionLayout( modifier = modifier.fillMaxSize(), currentScene = currentScene, - onChangeScene = setCurrentScene, + onChangeScene = { sceneKey -> viewModel.onSceneChanged(sceneKey.toCommunalSceneKey()) }, transitions = sceneTransitions, ) { - scene(Scenes.Blank, userActions = mapOf(Swipe.Left to Scenes.Communal)) { + scene( + TransitionSceneKey.Blank, + userActions = mapOf(Swipe.Left to TransitionSceneKey.Communal) + ) { BlankScene { showSceneTransitionLayout = false } } scene( - Scenes.Communal, - userActions = mapOf(Swipe.Right to Scenes.Blank), + TransitionSceneKey.Communal, + userActions = mapOf(Swipe.Right to TransitionSceneKey.Blank), ) { CommunalScene(viewModel, modifier = modifier) } @@ -121,3 +128,17 @@ private fun SceneScope.CommunalScene( ) { Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) } } + +// TODO(b/293899074): Remove these conversions once Compose can be used throughout SysUI. +object TransitionSceneKey { + val Blank = CommunalSceneKey.Blank.toTransitionSceneKey() + val Communal = CommunalSceneKey.Communal.toTransitionSceneKey() +} + +fun CommunalSceneKey.toTransitionSceneKey(): SceneKey { + return SceneKey(name = toString(), identity = this) +} + +fun SceneKey.toCommunalSceneKey(): CommunalSceneKey { + return this.identity as CommunalSceneKey +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt index 485e5122cfad..9cab17e9f6c4 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt @@ -1,15 +1,28 @@ package com.android.systemui.communal.data.repository import com.android.systemui.FeatureFlags +import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow /** Encapsulates the state of communal mode. */ interface CommunalRepository { /** Whether communal features are enabled. */ val isCommunalEnabled: Boolean + + /** + * Target scene as requested by the underlying [SceneTransitionLayout] or through + * [setDesiredScene]. + */ + val desiredScene: StateFlow<CommunalSceneKey> + + /** Updates the requested scene. */ + fun setDesiredScene(desiredScene: CommunalSceneKey) } @SysUISingleton @@ -23,4 +36,12 @@ constructor( get() = featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && featureFlags.communalHub() + + private val _desiredScene: MutableStateFlow<CommunalSceneKey> = + MutableStateFlow(CommunalSceneKey.Blank) + override val desiredScene: StateFlow<CommunalSceneKey> = _desiredScene.asStateFlow() + + override fun setDesiredScene(desiredScene: CommunalSceneKey) { + _desiredScene.value = desiredScene + } } 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 62387079ab35..ccccbb67c6c0 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 @@ -19,10 +19,13 @@ package com.android.systemui.communal.domain.interactor import com.android.systemui.communal.data.repository.CommunalRepository import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map /** Encapsulates business-logic related to communal mode. */ @SysUISingleton @@ -47,4 +50,22 @@ constructor( * (have an allocated id). */ val widgetContent: Flow<List<CommunalWidgetContentModel>> = widgetRepository.communalWidgets + + /** + * Target scene as requested by the underlying [SceneTransitionLayout] or through + * [onSceneChanged]. + */ + val desiredScene: StateFlow<CommunalSceneKey> = communalRepository.desiredScene + + /** + * Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the + * [CommunalSceneKey.Communal]. + */ + val isCommunalShowing: Flow<Boolean> = + communalRepository.desiredScene.map { it == CommunalSceneKey.Communal } + + /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */ + fun onSceneChanged(newScene: CommunalSceneKey) { + communalRepository.setDesiredScene(newScene) + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt new file mode 100644 index 000000000000..2be909c8e6d0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt @@ -0,0 +1,32 @@ +/* + * 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.communal.shared.model + +/** Definition of the possible scenes for the communal UI. */ +sealed class CommunalSceneKey( + private val loggingName: String, +) { + /** The communal scene containing the hub UI. */ + object Communal : CommunalSceneKey("communal") + + /** The default scene, shows nothing and is only there to allow swiping to communal. */ + object Blank : CommunalSceneKey("blank") + + override fun toString(): String { + return loggingName + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 390b580bad28..de9b56364c24 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -20,11 +20,13 @@ import android.appwidget.AppWidgetHost import android.content.Context import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor +import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.ui.model.CommunalContentUiModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @SysUISingleton @@ -33,7 +35,7 @@ class CommunalViewModel constructor( @Application private val context: Context, private val appWidgetHost: AppWidgetHost, - communalInteractor: CommunalInteractor, + private val communalInteractor: CommunalInteractor, tutorialInteractor: CommunalTutorialInteractor, ) { /** Whether communal hub should show tutorial content. */ @@ -54,4 +56,9 @@ constructor( ) } } + + val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene + fun onSceneChanged(scene: CommunalSceneKey) { + communalInteractor.onSceneChanged(scene) + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt index f3d41aaf2221..955dbbf06122 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt @@ -35,7 +35,9 @@ import android.view.ViewGroupOverlay import androidx.annotation.VisibleForTesting import com.android.app.animation.Interpolators import com.android.keyguard.KeyguardViewController +import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle @@ -57,6 +59,8 @@ import com.android.systemui.tracing.traceSection import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.settings.SecureSettings import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch private val TAG: String = MediaHierarchyManager::class.java.simpleName @@ -96,11 +100,13 @@ constructor( private val mediaManager: MediaDataManager, private val keyguardViewController: KeyguardViewController, private val dreamOverlayStateController: DreamOverlayStateController, + private val communalInteractor: CommunalInteractor, configurationController: ConfigurationController, wakefulnessLifecycle: WakefulnessLifecycle, panelEventsEvents: ShadeStateEvents, private val secureSettings: SecureSettings, @Main private val handler: Handler, + @Application private val coroutineScope: CoroutineScope, private val splitShadeStateController: SplitShadeStateController, private val logger: MediaViewLogger, ) { @@ -209,7 +215,7 @@ constructor( else result.setIntersect(animationStartClipping, targetClipping) } - private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1) + private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_COMMUNAL_HUB + 1) /** * The last location where this view was at before going to the desired location. This is useful * for guided transitions. @@ -401,6 +407,9 @@ constructor( } } + /** Is the communal UI showing */ + private var isCommunalShowing: Boolean = false + /** * The current cross fade progress. 0.5f means it's just switching between the start and the end * location and the content is fully faded, while 0.75f means that we're halfway faded in again @@ -563,6 +572,14 @@ constructor( settingsObserver, UserHandle.USER_ALL ) + + // Listen to the communal UI state. + coroutineScope.launch { + communalInteractor.isCommunalShowing.collect { value -> + isCommunalShowing = value + updateDesiredLocation(forceNoAnimation = true) + } + } } private fun updateConfiguration() { @@ -1115,6 +1132,9 @@ constructor( qsExpansion > 0.4f && onLockscreen -> LOCATION_QS onLockscreen && isSplitShadeExpanding() -> LOCATION_QS onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS + // TODO(b/308813166): revisit logic once interactions between the hub and + // shade/keyguard state are finalized + isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN else -> LOCATION_QQS } @@ -1224,6 +1244,9 @@ constructor( /** Attached on the dream overlay */ const val LOCATION_DREAM_OVERLAY = 3 + /** Attached to a view in the communal UI grid */ + const val LOCATION_COMMUNAL_HUB = 4 + /** Attached at the root of the hierarchy in an overlay */ const val IN_OVERLAY = -1000 @@ -1261,7 +1284,8 @@ private annotation class TransformationType MediaHierarchyManager.LOCATION_QS, MediaHierarchyManager.LOCATION_QQS, MediaHierarchyManager.LOCATION_LOCKSCREEN, - MediaHierarchyManager.LOCATION_DREAM_OVERLAY + MediaHierarchyManager.LOCATION_DREAM_OVERLAY, + MediaHierarchyManager.LOCATION_COMMUNAL_HUB ] ) @Retention(AnnotationRetention.SOURCE) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt index 20ea60fa3a5f..16a703a6bfdd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt @@ -20,11 +20,11 @@ import com.android.internal.logging.InstanceId import com.android.internal.logging.InstanceIdSequence import com.android.internal.logging.UiEvent import com.android.internal.logging.UiEventLogger -import com.android.systemui.res.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.ui.MediaHierarchyManager import com.android.systemui.media.controls.ui.MediaLocation +import com.android.systemui.res.R import java.lang.IllegalArgumentException import javax.inject.Inject @@ -154,6 +154,8 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN MediaHierarchyManager.LOCATION_DREAM_OVERLAY -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM + MediaHierarchyManager.LOCATION_COMMUNAL_HUB -> + MediaUiEvent.MEDIA_CAROUSEL_LOCATION_COMMUNAL else -> throw IllegalArgumentException("Unknown media carousel location $location") } logger.log(event) @@ -276,6 +278,8 @@ enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum { MEDIA_CAROUSEL_LOCATION_LOCKSCREEN(1039), @UiEvent(doc = "The media carousel moved to the dream state") MEDIA_CAROUSEL_LOCATION_DREAM(1040), + @UiEvent(doc = "The media carousel moved to the communal hub UI") + MEDIA_CAROUSEL_LOCATION_COMMUNAL(1520), @UiEvent(doc = "A media recommendation card was added to the media carousel") MEDIA_RECOMMENDATION_ADDED(1041), @UiEvent(doc = "A media recommendation card was removed from the media carousel") diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index 888cd0bf8b9a..8f752e59e806 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -46,6 +46,7 @@ public interface MediaModule { String QUICK_QS_PANEL = "media_quick_qs_panel"; String KEYGUARD = "media_keyguard"; String DREAM = "dream"; + String COMMUNAL_HUB = "communal_Hub"; /** */ @Provides @@ -87,6 +88,16 @@ public interface MediaModule { return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager); } + /** */ + @Provides + @SysUISingleton + @Named(COMMUNAL_HUB) + static MediaHost providesCommunalMediaHost(MediaHost.MediaHostStateHolder stateHolder, + MediaHierarchyManager hierarchyManager, MediaDataManager dataManager, + MediaHostStatesManager statesManager) { + return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager); + } + /** Provides a logging buffer related to the media tap-to-transfer chip on the sender device. */ @Provides @SysUISingleton diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 8e21f294a361..2f17b6fa35ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -23,6 +23,7 @@ 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.shared.model.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.coroutines.collectLastValue import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -86,4 +87,48 @@ class CommunalInteractorTest : SysuiTestCase() { val interactor = CommunalInteractor(communalRepository, widgetRepository) assertThat(interactor.isCommunalEnabled).isFalse() } + + @Test + fun listensToSceneChange() = + testScope.runTest { + val interactor = CommunalInteractor(communalRepository, widgetRepository) + var desiredScene = collectLastValue(interactor.desiredScene) + runCurrent() + assertThat(desiredScene()).isEqualTo(CommunalSceneKey.Blank) + + val targetScene = CommunalSceneKey.Communal + communalRepository.setDesiredScene(targetScene) + desiredScene = collectLastValue(interactor.desiredScene) + runCurrent() + assertThat(desiredScene()).isEqualTo(targetScene) + } + + @Test + fun updatesScene() = + testScope.runTest { + val interactor = CommunalInteractor(communalRepository, widgetRepository) + val targetScene = CommunalSceneKey.Communal + + interactor.onSceneChanged(targetScene) + + val desiredScene = collectLastValue(communalRepository.desiredScene) + runCurrent() + assertThat(desiredScene()).isEqualTo(targetScene) + } + + @Test + fun isCommunalShowing() = + testScope.runTest { + val interactor = CommunalInteractor(communalRepository, widgetRepository) + + var isCommunalShowing = collectLastValue(interactor.isCommunalShowing) + runCurrent() + assertThat(isCommunalShowing()).isEqualTo(false) + + interactor.onSceneChanged(CommunalSceneKey.Communal) + + isCommunalShowing = collectLastValue(interactor.isCommunalShowing) + runCurrent() + assertThat(isCommunalShowing()).isEqualTo(true) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt index 5296f1abd756..5bfe56931bb4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt @@ -25,6 +25,10 @@ import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController 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.domain.interactor.CommunalInteractor +import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle @@ -46,6 +50,11 @@ import com.android.systemui.util.mockito.nullable import com.android.systemui.util.settings.FakeSettings import com.android.systemui.utils.os.FakeHandler import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.Rule @@ -63,6 +72,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper @@ -71,6 +81,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Mock private lateinit var lockHost: MediaHost @Mock private lateinit var qsHost: MediaHost @Mock private lateinit var qqsHost: MediaHost + @Mock private lateinit var hubModeHost: MediaHost @Mock private lateinit var bypassController: KeyguardBypassController @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController @@ -93,10 +104,15 @@ class MediaHierarchyManagerTest : SysuiTestCase() { private lateinit var mediaHierarchyManager: MediaHierarchyManager private lateinit var mediaFrame: ViewGroup private val configurationController = FakeConfigurationController() + private val communalRepository = FakeCommunalRepository(isCommunalEnabled = true) + private val communalInteractor = + CommunalInteractor(communalRepository, FakeCommunalWidgetRepository()) private val notifPanelEvents = ShadeExpansionStateManager() private val settings = FakeSettings() private lateinit var testableLooper: TestableLooper private lateinit var fakeHandler: FakeHandler + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) @Before fun setup() { @@ -117,11 +133,13 @@ class MediaHierarchyManagerTest : SysuiTestCase() { mediaDataManager, keyguardViewController, dreamOverlayStateController, + communalInteractor, configurationController, wakefulnessLifecycle, notifPanelEvents, settings, fakeHandler, + testScope.backgroundScope, ResourcesSplitShadeStateController(), logger, ) @@ -131,6 +149,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP) setupHost(qsHost, MediaHierarchyManager.LOCATION_QS, QS_TOP) setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP) + setupHost(hubModeHost, MediaHierarchyManager.LOCATION_COMMUNAL_HUB, COMMUNAL_TOP) whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) whenever(mediaDataManager.hasActiveMedia()).thenReturn(true) whenever(mediaCarouselController.mediaCarouselScrollHandler) @@ -475,6 +494,33 @@ class MediaHierarchyManagerTest : SysuiTestCase() { } @Test + fun testCommunalLocation() = + testScope.runTest { + communalInteractor.onSceneChanged(CommunalSceneKey.Communal) + runCurrent() + verify(mediaCarouselController) + .onDesiredLocationChanged( + eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB), + nullable(), + eq(false), + anyLong(), + anyLong() + ) + clearInvocations(mediaCarouselController) + + communalInteractor.onSceneChanged(CommunalSceneKey.Blank) + runCurrent() + verify(mediaCarouselController) + .onDesiredLocationChanged( + eq(MediaHierarchyManager.LOCATION_QQS), + any(MediaHostState::class.java), + eq(false), + anyLong(), + anyLong() + ) + } + + @Test fun testQsExpandedChanged_noQqsMedia() { // When we are looking at QQS with active media whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) @@ -538,5 +584,6 @@ class MediaHierarchyManagerTest : SysuiTestCase() { private const val QQS_TOP = 123 private const val QS_TOP = 456 private const val LOCKSCREEN_TOP = 789 + private const val COMMUNAL_TOP = 111 } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt index e1c6ddefc13b..799bb403a8ff 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt @@ -1,8 +1,17 @@ package com.android.systemui.communal.data.repository +import com.android.systemui.communal.shared.model.CommunalSceneKey +import kotlinx.coroutines.flow.MutableStateFlow + /** Fake implementation of [CommunalRepository]. */ -class FakeCommunalRepository : CommunalRepository { - override var isCommunalEnabled = false +class FakeCommunalRepository( + override var isCommunalEnabled: Boolean = false, + override val desiredScene: MutableStateFlow<CommunalSceneKey> = + MutableStateFlow(CommunalSceneKey.Blank) +) : CommunalRepository { + override fun setDesiredScene(desiredScene: CommunalSceneKey) { + this.desiredScene.value = desiredScene + } fun setIsCommunalEnabled(value: Boolean) { isCommunalEnabled = value |