summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Beth Thibodeau <ethibodeau@google.com> 2023-11-09 13:29:14 -0600
committer Beth Thibodeau <ethibodeau@google.com> 2023-11-29 11:06:48 -0600
commit5d7575c445ca3dc52a8b3746f60046aee28c0572 (patch)
tree7c59c4235dac31a591f6aa2da2e60a0fb30f315a
parentf835f34db10a1f2194a079e66615bb12de7d29fe (diff)
Add media carousel to shade scene
Create a new composable, MediaCarousel, to handle placing the existing media carousel view into the shade scene. ShadeScene replaces the existing QQS media host location, but skips calculating view measurements in favor of\ the layout size provided by the scene framework Bug: 296122467 Test: atest SystemUITests Test: manual Flag: ACONFIG com.android.systemui.media_in_scene_container DEVELOPMENT Change-Id: If68e79fcfe71b6055708eac6250a58dfa0312561
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt50
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt50
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt4
12 files changed, 308 insertions, 21 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
new file mode 100644
index 000000000000..735c43356a40
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.media.controls.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.media.controls.ui.MediaCarouselController
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.util.animation.MeasurementInput
+
+private object MediaCarousel {
+ object Elements {
+ internal val Content = ElementKey("MediaCarouselContent")
+ }
+}
+
+@Composable
+fun SceneScope.MediaCarousel(
+ mediaHost: MediaHost,
+ modifier: Modifier = Modifier,
+ layoutWidth: Int,
+ layoutHeight: Int,
+ carouselController: MediaCarouselController,
+) {
+ // Notify controller to size the carousel for the current space
+ mediaHost.measurementInput = MeasurementInput(layoutWidth, layoutHeight)
+ carouselController.setSceneContainerSize(layoutWidth, layoutHeight)
+
+ AndroidView(
+ modifier = modifier.element(MediaCarousel.Elements.Content),
+ factory = { _ -> carouselController.mediaFrame },
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 2df151bc2d8e..fcb1619b4804 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -23,23 +23,34 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.media.controls.ui.MediaCarouselController
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
+import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.notifications.ui.composable.NotificationStack
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -49,7 +60,9 @@ import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.util.animation.MeasurementInput
import javax.inject.Inject
+import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -59,6 +72,7 @@ import kotlinx.coroutines.flow.stateIn
object Shade {
object Elements {
val QuickSettings = ElementKey("ShadeQuickSettings")
+ val MediaCarousel = ElementKey("ShadeMediaCarousel")
val Scrim = ElementKey("ShadeScrim")
val ScrimBackground = ElementKey("ShadeScrimBackground")
}
@@ -87,6 +101,8 @@ constructor(
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
private val statusBarIconController: StatusBarIconController,
+ private val mediaCarouselController: MediaCarouselController,
+ @Named(QUICK_QS_PANEL) private val mediaHost: MediaHost,
) : ComposableScene {
override val key = SceneKey.Shade
@@ -108,6 +124,8 @@ constructor(
createTintedIconManager = tintedIconManagerFactory::create,
createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
statusBarIconController = statusBarIconController,
+ mediaCarouselController = mediaCarouselController,
+ mediaHost = mediaHost,
modifier = modifier,
)
@@ -127,8 +145,12 @@ private fun SceneScope.ShadeScene(
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
statusBarIconController: StatusBarIconController,
+ mediaCarouselController: MediaCarouselController,
+ mediaHost: MediaHost,
modifier: Modifier = Modifier,
) {
+ val layoutWidth = remember { mutableStateOf(0) }
+
Box(modifier.element(Shade.Elements.Scrim)) {
Spacer(
modifier =
@@ -159,6 +181,34 @@ private fun SceneScope.ShadeScene(
viewModel.qsSceneAdapter,
QSSceneAdapter.State.QQS
)
+
+ if (viewModel.isMediaVisible()) {
+ val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
+ MediaCarousel(
+ modifier =
+ Modifier.height(mediaHeight).fillMaxWidth().layout { measurable, constraints
+ ->
+ val placeable = measurable.measure(constraints)
+
+ // Notify controller to size the carousel for the current space
+ mediaHost.measurementInput =
+ MeasurementInput(placeable.width, placeable.height)
+ mediaCarouselController.setSceneContainerSize(
+ placeable.width,
+ placeable.height
+ )
+
+ layout(placeable.width, placeable.height) {
+ placeable.placeRelative(0, 0)
+ }
+ },
+ mediaHost = mediaHost,
+ layoutWidth = layoutWidth.value,
+ layoutHeight = with(LocalDensity.current) { mediaHeight.toPx() }.toInt(),
+ carouselController = mediaCarouselController,
+ )
+ }
+
Spacer(modifier = Modifier.height(16.dp))
NotificationStack(
viewModel = viewModel.notifications,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 18b7168098ab..9a748b962943 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -37,6 +37,8 @@ import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.model.SysUiState
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -188,6 +190,9 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { _, _ -> mock<View>() })
+ @Mock private lateinit var mediaDataManager: MediaDataManager
+ @Mock private lateinit var mediaHost: MediaHost
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -240,6 +245,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = utils.notificationsPlaceholderViewModel(),
+ mediaDataManager = mediaDataManager,
+ mediaHost = mediaHost,
)
utils.deviceEntryRepository.setUnlocked(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index e2640af136a7..a2946cc90bee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -23,6 +23,8 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
@@ -35,6 +37,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsVi
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -42,6 +45,8 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -83,8 +88,12 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
private lateinit var underTest: ShadeSceneViewModel
+ @Mock private lateinit var mediaDataManager: MediaDataManager
+ @Mock private lateinit var mediaHost: MediaHost
+
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
shadeHeaderViewModel =
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
@@ -102,6 +111,8 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = utils.notificationsPlaceholderViewModel(),
+ mediaDataManager = mediaDataManager,
+ mediaHost = mediaHost,
)
}
@@ -174,4 +185,20 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
+
+ @Test
+ fun hasActiveMedia_mediaVisible() =
+ testScope.runTest {
+ whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
+
+ assertThat(underTest.isMediaVisible()).isTrue()
+ }
+
+ @Test
+ fun doesNotHaveActiveMedia_mediaNotVisible() =
+ testScope.runTest {
+ whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
+
+ assertThat(underTest.isMediaVisible()).isFalse()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index a2524709b8bc..9cdf85794088 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -271,6 +271,10 @@ constructor(
private val isReorderingAllowed: Boolean
get() = visualStabilityProvider.isReorderingAllowed
+ /** Size provided by the scene framework container */
+ private var widthInSceneContainerPx = 0
+ private var heightInSceneContainerPx = 0
+
init {
dumpManager.registerDumpable(TAG, this)
mediaFrame = inflateMediaCarousel()
@@ -581,6 +585,15 @@ constructor(
}
}
+ fun setSceneContainerSize(width: Int, height: Int) {
+ if (width == widthInSceneContainerPx && height == heightInSceneContainerPx) {
+ return
+ }
+ widthInSceneContainerPx = width
+ heightInSceneContainerPx = height
+ updatePlayers(recreateMedia = true)
+ }
+
private fun reorderAllPlayers(
previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?,
key: String? = null
@@ -638,6 +651,11 @@ constructor(
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
if (existingPlayer == null) {
val newPlayer = mediaControlPanelFactory.get()
+ if (mediaFlags.isSceneContainerEnabled()) {
+ newPlayer.mediaViewController.widthInSceneContainerPx = widthInSceneContainerPx
+ newPlayer.mediaViewController.heightInSceneContainerPx =
+ heightInSceneContainerPx
+ }
newPlayer.attachPlayer(
MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index cce4cda8af8a..04883c368b78 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -81,7 +81,6 @@ import com.android.internal.logging.InstanceId;
import com.android.internal.widget.CachingIconView;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.ActivityIntentHelper;
-import com.android.systemui.res.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
import com.android.systemui.bluetooth.BroadcastDialogController;
@@ -102,6 +101,7 @@ import com.android.systemui.media.controls.models.recommendation.RecommendationV
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.util.MediaDataUtils;
+import com.android.systemui.media.controls.util.MediaFlags;
import com.android.systemui.media.controls.util.MediaUiEventLogger;
import com.android.systemui.media.controls.util.SmallHash;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -109,6 +109,7 @@ import com.android.systemui.monet.ColorScheme;
import com.android.systemui.monet.Style;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.res.R;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -190,6 +191,7 @@ public class MediaControlPanel {
@VisibleForTesting static final long TURBULENCE_NOISE_PLAY_DURATION = 7500L;
private final SeekBarViewModel mSeekBarViewModel;
+ private final MediaFlags mMediaFlags;
private SeekBarObserver mSeekBarObserver;
protected final Executor mBackgroundExecutor;
private final DelayableExecutor mMainExecutor;
@@ -280,7 +282,8 @@ public class MediaControlPanel {
NotificationLockscreenUserManager lockscreenUserManager,
BroadcastDialogController broadcastDialogController,
FeatureFlags featureFlags,
- GlobalSettings globalSettings
+ GlobalSettings globalSettings,
+ MediaFlags mediaFlags
) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
@@ -299,6 +302,7 @@ public class MediaControlPanel {
mActivityIntentHelper = activityIntentHelper;
mLockscreenUserManager = lockscreenUserManager;
mBroadcastDialogController = broadcastDialogController;
+ mMediaFlags = mediaFlags;
mSeekBarViewModel.setLogSeek(() -> {
if (mPackageName != null && mInstanceId != null) {
@@ -575,7 +579,10 @@ public class MediaControlPanel {
// to something which might impact the measurement
// State refresh interferes with the translation animation, only run it if it's not running.
if (!mMetadataAnimationHandler.isRunning()) {
- mMediaViewController.refreshState();
+ // Don't refresh in scene framework, because it will calculate with invalid layout sizes
+ if (!mMediaFlags.isSceneContainerEnabled()) {
+ mMediaViewController.refreshState();
+ }
}
// Turbulence noise
@@ -805,7 +812,14 @@ public class MediaControlPanel {
// Capture width & height from views in foreground for artwork scaling in background
int width = mMediaViewHolder.getAlbumView().getMeasuredWidth();
int height = mMediaViewHolder.getAlbumView().getMeasuredHeight();
+ if (mMediaFlags.isSceneContainerEnabled() && (width <= 0 || height <= 0)) {
+ // TODO(b/312714128): ensure we have a valid size before setting background
+ width = mMediaViewController.getWidthInSceneContainerPx();
+ height = mMediaViewController.getHeightInSceneContainerPx();
+ }
+ final int finalWidth = width;
+ final int finalHeight = height;
mBackgroundExecutor.execute(() -> {
// Album art
ColorScheme mutableColorScheme = null;
@@ -815,7 +829,8 @@ public class MediaControlPanel {
WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
if (wallpaperColors != null) {
mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
- artwork = addGradientToPlayerAlbum(artworkIcon, mutableColorScheme, width, height);
+ artwork = addGradientToPlayerAlbum(artworkIcon, mutableColorScheme, finalWidth,
+ finalHeight);
isArtworkBound = true;
} else {
// If there's no artwork, use colors from the app icon
@@ -857,8 +872,10 @@ public class MediaControlPanel {
TransitionDrawable transitionDrawable = new TransitionDrawable(
new Drawable[]{mPrevArtwork, artwork});
- scaleTransitionDrawableLayer(transitionDrawable, 0, width, height);
- scaleTransitionDrawableLayer(transitionDrawable, 1, width, height);
+ scaleTransitionDrawableLayer(transitionDrawable, 0, finalWidth,
+ finalHeight);
+ scaleTransitionDrawableLayer(transitionDrawable, 1, finalWidth,
+ finalHeight);
transitionDrawable.setLayerGravity(0, Gravity.CENTER);
transitionDrawable.setLayerGravity(1, Gravity.CENTER);
transitionDrawable.setCrossFadeEnabled(true);
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 9d6e9b4f3ed5..35456d5ee472 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
@@ -43,6 +43,7 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
@@ -108,6 +109,7 @@ constructor(
@Application private val coroutineScope: CoroutineScope,
private val splitShadeStateController: SplitShadeStateController,
private val logger: MediaViewLogger,
+ private val mediaFlags: MediaFlags,
) {
/** Track the media player setting status on lock screen. */
@@ -215,6 +217,7 @@ constructor(
}
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.
@@ -1041,6 +1044,17 @@ constructor(
private fun updateHostAttachment() =
traceSection("MediaHierarchyManager#updateHostAttachment") {
+ if (mediaFlags.isSceneContainerEnabled()) {
+ // No need to manage transition states - just update the desired location directly
+ logger.logMediaHostAttachment(desiredLocation)
+ mediaCarouselController.onDesiredLocationChanged(
+ desiredLocation = desiredLocation,
+ desiredHostState = getHost(desiredLocation),
+ animate = false,
+ )
+ return
+ }
+
var newLocation = resolveLocationForFading()
// Don't use the overlay when fading or when we don't have active media
var canUseOverlay = !isCurrentlyFading() && hasActiveMediaOrRecommendation
@@ -1124,6 +1138,7 @@ constructor(
(!bypassController.bypassEnabled && (statusbarState == StatusBarState.KEYGUARD))
val location =
when {
+ mediaFlags.isSceneContainerEnabled() -> desiredLocation
dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
(qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
@@ -1282,7 +1297,7 @@ private annotation class TransformationType
MediaHierarchyManager.LOCATION_QQS,
MediaHierarchyManager.LOCATION_LOCKSCREEN,
MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
- MediaHierarchyManager.LOCATION_COMMUNAL_HUB
+ MediaHierarchyManager.LOCATION_COMMUNAL_HUB,
]
)
@Retention(AnnotationRetention.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index d277f32d5e54..a99c51c2e557 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -28,6 +28,7 @@ import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionLayoutController
@@ -207,6 +208,10 @@ constructor(
var isGutsVisible = false
private set
+ /** Size provided by the scene framework container */
+ var widthInSceneContainerPx = 0
+ var heightInSceneContainerPx = 0
+
init {
mediaHostStatesManager.addController(this)
layoutController.sizeChangedListener = { width: Int, height: Int ->
@@ -420,6 +425,10 @@ constructor(
state: MediaHostState?,
isGutsAnimation: Boolean = false
): TransitionViewState? {
+ if (mediaFlags.isSceneContainerEnabled()) {
+ return obtainSceneContainerViewState()
+ }
+
if (state == null || state.measurementInput == null) {
return null
}
@@ -670,6 +679,24 @@ constructor(
refreshState()
}
+ /** Get a view state based on the width and height set by the scene */
+ private fun obtainSceneContainerViewState(): TransitionViewState? {
+ logger.logMediaSize("scene container", widthInSceneContainerPx, heightInSceneContainerPx)
+
+ // Similar to obtainViewState: Let's create a new measurement
+ val result =
+ transitionLayout?.calculateViewState(
+ MeasurementInput(widthInSceneContainerPx, heightInSceneContainerPx),
+ expandedLayout,
+ TransitionViewState()
+ )
+ result?.let {
+ // And then ensure the guts visibility is set correctly
+ setGutsViewState(it)
+ }
+ return result
+ }
+
/**
* Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation]. In the event
* of [location] not being visible, [locationWhenHidden] will be used instead.
@@ -681,6 +708,10 @@ constructor(
*/
private fun obtainViewStateForLocation(@MediaLocation location: Int): TransitionViewState? {
val mediaHostState = mediaHostStatesManager.mediaHostStates[location] ?: return null
+ if (mediaFlags.isSceneContainerEnabled()) {
+ return obtainSceneContainerViewState()
+ }
+
val viewState = obtainViewState(mediaHostState)
if (viewState != null) {
// update the size of the viewstate for the location with the override
@@ -708,6 +739,21 @@ constructor(
/** Clear all existing measurements and refresh the state to match the view. */
fun refreshState() =
traceSection("MediaViewController#refreshState") {
+ if (mediaFlags.isSceneContainerEnabled()) {
+ // We don't need to recreate measurements for scene container, since it's a known
+ // size. Just get the view state and update the layout controller
+ obtainSceneContainerViewState()?.let {
+ // Get scene container state, then setCurrentState
+ layoutController.setState(
+ state = it,
+ applyImmediately = true,
+ animate = false,
+ isGuts = false,
+ )
+ }
+ return
+ }
+
// Let's clear all of our measurements and recreate them!
viewStates.clear()
if (firstRefresh) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 44232ffb3ad7..15747b9eb515 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -55,7 +55,7 @@ constructor(
/** Check whether we allow remote media to generate resume controls */
fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
- /** Check whether to use flexiglass layout */
- fun isFlexiglassEnabled() =
+ /** Check whether to use scene framework */
+ fun isSceneContainerEnabled() =
sceneContainerFlags.isEnabled() && MediaInSceneContainerFlag.isEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 0065db370b12..d0da945ca7d0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -19,10 +19,16 @@ package com.android.systemui.shade.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.MediaHostState
+import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
+import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -39,6 +45,8 @@ constructor(
val qsSceneAdapter: QSSceneAdapter,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val notifications: NotificationsPlaceholderViewModel,
+ val mediaDataManager: MediaDataManager,
+ @Named(MediaModule.QUICK_QS_PANEL) private val mediaHost: MediaHost,
) {
/** The key of the scene we should switch to when swiping up. */
val upDestinationSceneKey: StateFlow<SceneKey> =
@@ -74,4 +82,15 @@ constructor(
else -> SceneKey.Lockscreen
}
}
+
+ init {
+ mediaHost.expansion = MediaHostState.EXPANDED
+ mediaHost.showsOnlyActiveMedia = true
+ mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+ }
+
+ fun isMediaVisible(): Boolean {
+ // TODO(b/296122467): handle updates to carousel visibility while scene is still visible
+ return mediaDataManager.hasActiveMediaOrRecommendation()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index d6e2e973b29d..2f35380e562b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -61,7 +61,6 @@ import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.internal.widget.CachingIconView
import com.android.systemui.ActivityIntentHelper
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.bluetooth.BroadcastDialogController
import com.android.systemui.broadcast.BroadcastSender
@@ -81,12 +80,14 @@ import com.android.systemui.media.controls.models.recommendation.RecommendationV
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.monet.ColorScheme
import com.android.systemui.monet.Style
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.surfaceeffects.ripple.MultiRippleView
@@ -232,6 +233,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
this.set(Flags.UMO_TURBULENCE_NOISE, false)
}
@Mock private lateinit var globalSettings: GlobalSettings
+ @Mock private lateinit var mediaFlags: MediaFlags
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -251,6 +253,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
.thenReturn(applicationInfo)
whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE)
context.setMockPackageManager(packageManager)
+ whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false)
player =
object :
@@ -273,7 +276,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
lockscreenUserManager,
broadcastDialogController,
fakeFeatureFlag,
- globalSettings
+ globalSettings,
+ mediaFlags,
) {
override fun loadAnimator(
animId: Int,
@@ -1884,7 +1888,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindRecommendation_listHasTooFewRecs_notDisplayed() {
player.attachRecommendation(recommendationViewHolder)
- val icon = Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
+ val icon =
+ Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
val data =
smartspaceData.copy(
recommendations =
@@ -1911,7 +1916,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindRecommendation_listHasTooFewRecsWithIcons_notDisplayed() {
player.attachRecommendation(recommendationViewHolder)
- val icon = Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
+ val icon =
+ Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
val data =
smartspaceData.copy(
recommendations =
@@ -1955,7 +1961,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
val subtitle1 = "Subtitle1"
val subtitle2 = "Subtitle2"
val subtitle3 = "Subtitle3"
- val icon = Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
+ val icon =
+ Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
val data =
smartspaceData.copy(
@@ -1998,7 +2005,12 @@ public class MediaControlPanelTest : SysuiTestCase() {
listOf(
SmartspaceAction.Builder("id1", "")
.setSubtitle("fake subtitle")
- .setIcon(Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata))
+ .setIcon(
+ Icon.createWithResource(
+ context,
+ com.android.settingslib.R.drawable.ic_1x_mobiledata
+ )
+ )
.setExtras(Bundle.EMPTY)
.build()
)
@@ -2013,7 +2025,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
useRealConstraintSets()
player.attachRecommendation(recommendationViewHolder)
- val icon = Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
+ val icon =
+ Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
val data =
smartspaceData.copy(
recommendations =
@@ -2047,7 +2060,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
useRealConstraintSets()
player.attachRecommendation(recommendationViewHolder)
- val icon = Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
+ val icon =
+ Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
val data =
smartspaceData.copy(
recommendations =
@@ -2086,7 +2100,12 @@ public class MediaControlPanelTest : SysuiTestCase() {
listOf(
SmartspaceAction.Builder("id1", "title1")
.setSubtitle("")
- .setIcon(Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata))
+ .setIcon(
+ Icon.createWithResource(
+ context,
+ com.android.settingslib.R.drawable.ic_1x_mobiledata
+ )
+ )
.setExtras(Bundle.EMPTY)
.build(),
SmartspaceAction.Builder("id2", "title2")
@@ -2096,7 +2115,12 @@ public class MediaControlPanelTest : SysuiTestCase() {
.build(),
SmartspaceAction.Builder("id3", "title3")
.setSubtitle("")
- .setIcon(Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_3g_mobiledata))
+ .setIcon(
+ Icon.createWithResource(
+ context,
+ com.android.settingslib.R.drawable.ic_3g_mobiledata
+ )
+ )
.setExtras(Bundle.EMPTY)
.build()
)
@@ -2119,7 +2143,12 @@ public class MediaControlPanelTest : SysuiTestCase() {
listOf(
SmartspaceAction.Builder("id1", "")
.setSubtitle("subtitle1")
- .setIcon(Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata))
+ .setIcon(
+ Icon.createWithResource(
+ context,
+ com.android.settingslib.R.drawable.ic_1x_mobiledata
+ )
+ )
.setExtras(Bundle.EMPTY)
.build(),
SmartspaceAction.Builder("id2", "")
@@ -2129,7 +2158,12 @@ public class MediaControlPanelTest : SysuiTestCase() {
.build(),
SmartspaceAction.Builder("id3", "")
.setSubtitle("subtitle3")
- .setIcon(Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_3g_mobiledata))
+ .setIcon(
+ Icon.createWithResource(
+ context,
+ com.android.settingslib.R.drawable.ic_3g_mobiledata
+ )
+ )
.setExtras(Bundle.EMPTY)
.build()
)
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 db7c9876f21b..73885f4d428d 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
@@ -32,6 +32,7 @@ import com.android.systemui.controls.controller.ControlsControllerImplTest.Compa
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
@@ -94,6 +95,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
@Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock lateinit var logger: MediaViewLogger
+ @Mock private lateinit var mediaFlags: MediaFlags
@Captor
private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
@Captor
@@ -126,6 +128,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
isQsBypassingShade = MutableStateFlow(false)
whenever(shadeInteractor.isQsBypassingShade).thenReturn(isQsBypassingShade)
+ whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false)
mediaHierarchyManager =
MediaHierarchyManager(
context,
@@ -145,6 +148,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
testScope.backgroundScope,
ResourcesSplitShadeStateController(),
logger,
+ mediaFlags,
)
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
verify(statusBarStateController).addCallback(statusBarCallback.capture())