diff options
10 files changed, 298 insertions, 96 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 index f4d9e820ad8f..3d8ca1e96a09 100644 --- 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 @@ -22,7 +22,9 @@ import android.widget.FrameLayout  import androidx.compose.foundation.layout.fillMaxSize  import androidx.compose.foundation.layout.fillMaxWidth  import androidx.compose.foundation.layout.height +import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass  import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember  import androidx.compose.ui.Modifier  import androidx.compose.ui.layout.approachLayout  import androidx.compose.ui.layout.layout @@ -31,8 +33,12 @@ import androidx.compose.ui.unit.IntOffset  import androidx.compose.ui.viewinterop.AndroidView  import com.android.compose.animation.scene.MovableElementKey  import com.android.compose.animation.scene.SceneScope +import com.android.compose.windowsizeclass.LocalWindowSizeClass +import com.android.internal.R.attr.layout +import com.android.systemui.media.controls.ui.composable.MediaCarouselStateLoader.stateForMediaCarouselContent  import com.android.systemui.media.controls.ui.controller.MediaCarouselController  import com.android.systemui.media.controls.ui.view.MediaHost +import com.android.systemui.media.controls.ui.view.MediaHostState  import com.android.systemui.res.R  import com.android.systemui.util.animation.MeasurementInput @@ -53,12 +59,20 @@ fun SceneScope.MediaCarousel(      modifier: Modifier = Modifier,      carouselController: MediaCarouselController,      offsetProvider: (() -> IntOffset)? = null, +    usingCollapsedLandscapeMedia: Boolean = false,  ) {      if (!isVisible || carouselController.isLockedAndHidden()) {          return      } -    val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded) +    val carouselState = remember { { stateForMediaCarouselContent() } } +    val isCollapsed = usingCollapsedLandscapeMedia && isLandscape() +    val mediaHeight = +        if (isCollapsed && mediaHost.expansion == MediaHostState.COLLAPSED) { +            dimensionResource(R.dimen.qs_media_session_height_collapsed) +        } else { +            dimensionResource(R.dimen.qs_media_session_height_expanded) +        }      MovableElement(          key = MediaCarousel.Elements.Content, @@ -95,6 +109,7 @@ fun SceneScope.MediaCarousel(                              }                          },                  factory = { context -> +                    MediaCarouselStateLoader.loadCarouselState(carouselController, carouselState())                      FrameLayout(context).apply {                          layoutParams =                              FrameLayout.LayoutParams( @@ -103,7 +118,10 @@ fun SceneScope.MediaCarousel(                              )                      }                  }, -                update = { it.setView(carouselController.mediaFrame) }, +                update = { +                    MediaCarouselStateLoader.loadCarouselState(carouselController, carouselState()) +                    it.setView(carouselController.mediaFrame) +                },                  onRelease = { it.removeAllViews() },              )          } @@ -117,3 +135,8 @@ private fun ViewGroup.setView(view: View) {      (view.parent as? ViewGroup)?.removeView(view)      addView(view)  } + +@Composable +fun SceneScope.isLandscape(): Boolean { +    return LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt new file mode 100644 index 000000000000..4a0136c40c14 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarouselStateLoader.kt @@ -0,0 +1,153 @@ +/* + * 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.media.controls.ui.composable + +import com.android.compose.animation.scene.ContentKey +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.content.state.TransitionState +import com.android.systemui.media.controls.ui.controller.MediaCarouselController +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager +import com.android.systemui.media.controls.ui.controller.MediaLocation +import com.android.systemui.scene.shared.model.Overlays +import com.android.systemui.scene.shared.model.Scenes +import kotlin.math.min + +object MediaCarouselStateLoader { + +    /** Sets current state for media carousel. */ +    fun loadCarouselState(carouselController: MediaCarouselController, state: State) { +        if (state is State.Gone) return + +        carouselController.setCurrentState( +            state.startLocation, +            state.endLocation, +            state.transitionProgress, +            immediately = true, +        ) +    } + +    /** Returns the corresponding media location for the given [scene] */ +    @MediaLocation +    private fun getMediaLocation(scene: SceneKey): Int { +        return when (scene) { +            Scenes.QuickSettings -> MediaHierarchyManager.LOCATION_QS +            Scenes.Shade -> MediaHierarchyManager.LOCATION_QQS +            Scenes.Lockscreen -> MediaHierarchyManager.LOCATION_LOCKSCREEN +            Scenes.Communal -> MediaHierarchyManager.LOCATION_COMMUNAL_HUB +            else -> MediaHierarchyManager.LOCATION_UNKNOWN +        } +    } + +    /** Returns the corresponding media location for the given [content] */ +    @MediaLocation +    private fun getMediaLocation(content: ContentKey): Int { +        return when (content) { +            Overlays.QuickSettingsShade -> MediaHierarchyManager.LOCATION_QS +            Overlays.NotificationsShade -> MediaHierarchyManager.LOCATION_QQS +            else -> MediaHierarchyManager.LOCATION_UNKNOWN +        } +    } + +    /** State for media carousel. */ +    sealed interface State { +        val transitionProgress: Float +        // TODO b/368368388: implement media squishiness +        val squishFraction: () -> Float +        @MediaLocation val startLocation: Int +        @MediaLocation val endLocation: Int + +        /** State when media carousel is not visible on screen. */ +        data object Gone : State { +            override val transitionProgress: Float = 1.0F +            override val squishFraction: () -> Float = { 1.0F } +            override val endLocation: Int = MediaHierarchyManager.LOCATION_UNKNOWN +            override val startLocation: Int = MediaHierarchyManager.LOCATION_UNKNOWN +        } + +        /** State when media carousel is moving from one media location to another */ +        data class InProgress( +            override val transitionProgress: Float, +            override val startLocation: Int, +            override val endLocation: Int, +        ) : State { +            override val squishFraction = { 1.0F } +        } + +        /** State when media carousel reached the end location. */ +        data class Idle(override val endLocation: Int) : State { +            override val transitionProgress = 1.0F +            override val startLocation = MediaHierarchyManager.LOCATION_UNKNOWN +            override val squishFraction = { 1.0F } +        } +    } + +    /** Returns the state of media carousel */ +    fun SceneScope.stateForMediaCarouselContent(): State { +        return when (val transitionState = layoutState.transitionState) { +            is TransitionState.Idle -> { +                if (MediaContentPicker.contents.contains(transitionState.currentScene)) { +                    State.Idle(getMediaLocation(transitionState.currentScene)) +                } else { +                    State.Gone +                } +            } +            is TransitionState.Transition.ChangeScene -> +                with(transitionState) { +                    if ( +                        MediaContentPicker.contents.contains(toScene) && +                            MediaContentPicker.contents.contains(fromScene) +                    ) { +                        State.InProgress( +                            min(progress, 1.0F), +                            getMediaLocation(fromScene), +                            getMediaLocation(toScene), +                        ) +                    } else if (MediaContentPicker.contents.contains(toScene)) { +                        State.InProgress( +                            transitionProgress = 1.0F, +                            startLocation = MediaHierarchyManager.LOCATION_UNKNOWN, +                            getMediaLocation(toScene), +                        ) +                    } else { +                        State.Gone +                    } +                } +            is TransitionState.Transition.OverlayTransition -> +                with(transitionState) { +                    if ( +                        MediaContentPicker.contents.contains(toContent) && +                            MediaContentPicker.contents.contains(fromContent) +                    ) { +                        State.InProgress( +                            min(progress, 1.0F), +                            getMediaLocation(fromContent), +                            getMediaLocation(toContent), +                        ) +                    } else if (MediaContentPicker.contents.contains(toContent)) { +                        State.InProgress( +                            transitionProgress = 1.0F, +                            startLocation = MediaHierarchyManager.LOCATION_UNKNOWN, +                            getMediaLocation(toContent), +                        ) +                    } else { +                        State.Gone +                    } +                } +        } +    } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 0c69dbd5655c..630497998c3e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -48,7 +48,6 @@ import androidx.compose.foundation.layout.padding  import androidx.compose.foundation.layout.wrapContentHeight  import androidx.compose.foundation.rememberScrollState  import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass  import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass  import androidx.compose.runtime.Composable  import androidx.compose.runtime.DisposableEffect @@ -85,6 +84,7 @@ import com.android.systemui.dagger.SysUISingleton  import com.android.systemui.lifecycle.ExclusiveActivatable  import com.android.systemui.lifecycle.rememberViewModel  import com.android.systemui.media.controls.ui.composable.MediaCarousel +import com.android.systemui.media.controls.ui.composable.isLandscape  import com.android.systemui.media.controls.ui.controller.MediaCarouselController  import com.android.systemui.media.controls.ui.view.MediaHost  import com.android.systemui.media.dagger.MediaModule @@ -288,9 +288,7 @@ private fun SceneScope.QuickSettingsScene(          // ############# Media ###############          val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle() -        val mediaInRow = -            isMediaVisible && -                LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact +        val mediaInRow = isMediaVisible && isLandscape()          val mediaOffset by              animateSceneDpAsState(value = InQS, key = MediaLandscapeTopOffset, canOverflow = false) 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 8a59e204eb23..7f2ee2a8351a 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 @@ -43,7 +43,6 @@ import androidx.compose.foundation.layout.systemBars  import androidx.compose.foundation.rememberScrollState  import androidx.compose.foundation.shape.RoundedCornerShape  import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass  import androidx.compose.runtime.Composable  import androidx.compose.runtime.DisposableEffect  import androidx.compose.runtime.LaunchedEffect @@ -78,7 +77,6 @@ import com.android.compose.animation.scene.animateSceneFloatAsState  import com.android.compose.animation.scene.content.state.TransitionState  import com.android.compose.modifiers.padding  import com.android.compose.modifiers.thenIf -import com.android.compose.windowsizeclass.LocalWindowSizeClass  import com.android.systemui.battery.BatteryMeterViewController  import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation  import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout @@ -89,6 +87,7 @@ import com.android.systemui.lifecycle.ExclusiveActivatable  import com.android.systemui.lifecycle.rememberViewModel  import com.android.systemui.media.controls.ui.composable.MediaCarousel  import com.android.systemui.media.controls.ui.composable.MediaContentPicker +import com.android.systemui.media.controls.ui.composable.isLandscape  import com.android.systemui.media.controls.ui.composable.shouldElevateMedia  import com.android.systemui.media.controls.ui.controller.MediaCarouselController  import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager @@ -197,6 +196,8 @@ constructor(              qsMediaHost = qsMediaHost,              modifier = modifier,              shadeSession = shadeSession, +            usingCollapsedLandscapeMedia = +                Utils.useCollapsedMediaInLandscape(LocalContext.current.resources),          )      init { @@ -223,6 +224,7 @@ private fun SceneScope.ShadeScene(      qsMediaHost: MediaHost,      modifier: Modifier = Modifier,      shadeSession: SaveableSession, +    usingCollapsedLandscapeMedia: Boolean,  ) {      val view = LocalView.current      LaunchedEffect(Unit) { @@ -245,6 +247,7 @@ private fun SceneScope.ShadeScene(                  mediaHost = qqsMediaHost,                  modifier = modifier,                  shadeSession = shadeSession, +                usingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia,              )          is ShadeMode.Split ->              SplitShade( @@ -275,14 +278,11 @@ private fun SceneScope.SingleShade(      mediaHost: MediaHost,      modifier: Modifier = Modifier,      shadeSession: SaveableSession, +    usingCollapsedLandscapeMedia: Boolean,  ) {      val cutoutLocation = LocalDisplayCutout.current.location      val cutoutInsets = WindowInsets.Companion.displayCutout -    val isLandscape = LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact -    val usingCollapsedLandscapeMedia = -        Utils.useCollapsedMediaInLandscape(LocalContext.current.resources) -    val isExpanded = !usingCollapsedLandscapeMedia || !isLandscape -    mediaHost.expansion = if (isExpanded) EXPANDED else COLLAPSED +    mediaHost.expansion = if (usingCollapsedLandscapeMedia && isLandscape()) COLLAPSED else EXPANDED      var maxNotifScrimTop by remember { mutableIntStateOf(0) }      val tileSquishiness by @@ -298,7 +298,7 @@ private fun SceneScope.SingleShade(          layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||              layoutState.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade)      // Media is visible and we are in landscape on a small height screen -    val mediaInRow = isMediaVisible && isLandscape +    val mediaInRow = isMediaVisible && isLandscape()      val mediaOffset by          animateSceneDpAsState(value = InQQS, key = MediaLandscapeTopOffset, canOverflow = false) @@ -380,6 +380,7 @@ private fun SceneScope.SingleShade(                      mediaOffsetProvider = mediaOffsetProvider,                      carouselController = mediaCarouselController,                      modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.Media), +                    usingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia,                  )                  NotificationScrollingStack( @@ -636,6 +637,7 @@ private fun SceneScope.ShadeMediaCarousel(      carouselController: MediaCarouselController,      mediaOffsetProvider: ShadeMediaOffsetProvider,      modifier: Modifier = Modifier, +    usingCollapsedLandscapeMedia: Boolean = false,  ) {      MediaCarousel(          modifier = modifier.fillMaxWidth(), @@ -648,5 +650,6 @@ private fun SceneScope.ShadeMediaCarousel(              } else {                  { mediaOffsetProvider.offset }              }, +        usingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia,      )  } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index bb9517a14142..a0fb0bf25c7b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -110,6 +110,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged  import kotlinx.coroutines.flow.filter  import kotlinx.coroutines.flow.flowOn  import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge  import kotlinx.coroutines.flow.onStart  import kotlinx.coroutines.flow.stateIn  import kotlinx.coroutines.launch @@ -174,19 +175,21 @@ constructor(       * The desired location where we'll be at the end of the transformation. Usually this matches       * the end location, except when we're still waiting on a state update call.       */ -    @MediaLocation private var desiredLocation: Int = -1 +    @MediaLocation private var desiredLocation: Int = MediaHierarchyManager.LOCATION_UNKNOWN      /**       * The ending location of the view where it ends when all animations and transitions have       * finished       */ -    @MediaLocation @VisibleForTesting var currentEndLocation: Int = -1 +    @MediaLocation +    @VisibleForTesting +    var currentEndLocation: Int = MediaHierarchyManager.LOCATION_UNKNOWN      /**       * The ending location of the view where it ends when all animations and transitions have       * finished       */ -    @MediaLocation private var currentStartLocation: Int = -1 +    @MediaLocation private var currentStartLocation: Int = MediaHierarchyManager.LOCATION_UNKNOWN      /** The progress of the transition or 1.0 if there is no transition happening */      private var currentTransitionProgress: Float = 1.0f @@ -726,6 +729,13 @@ constructor(                      )                  DiffUtil.calculateDiff(diffUtilCallback).dispatchUpdatesTo(listUpdateCallback)                  setNewViewModelsList(it) + +                // Update host visibility when media changes. +                merge( +                        mediaCarouselViewModel.hasAnyMediaOrRecommendations, +                        mediaCarouselViewModel.hasActiveMediaOrRecommendations, +                    ) +                    .collect { updateHostVisibility() }              }          }      } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt index 38cea5b23f78..745ab12c27d6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt @@ -64,7 +64,6 @@ import com.android.systemui.util.settings.SecureSettings  import javax.inject.Inject  import kotlinx.coroutines.CoroutineScope  import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect  import kotlinx.coroutines.flow.collectLatest  import kotlinx.coroutines.flow.combine  import kotlinx.coroutines.flow.distinctUntilChanged @@ -161,10 +160,10 @@ constructor(      private var animationStartAlpha = 0.0f      /** The starting location of the cross fade if an animation is running right now. */ -    @MediaLocation private var crossFadeAnimationStartLocation = -1 +    @MediaLocation private var crossFadeAnimationStartLocation = LOCATION_UNKNOWN      /** The end location of the cross fade if an animation is running right now. */ -    @MediaLocation private var crossFadeAnimationEndLocation = -1 +    @MediaLocation private var crossFadeAnimationEndLocation = LOCATION_UNKNOWN      private var targetBounds: Rect = Rect()      private val mediaFrame          get() = mediaCarouselController.mediaFrame @@ -191,7 +190,7 @@ constructor(                      animationStartBounds,                      targetBounds,                      boundsProgress, -                    result = currentBounds +                    result = currentBounds,                  )                  resolveClipping(currentClipping)                  applyState(currentBounds, currentAlpha, clipBounds = currentClipping) @@ -233,16 +232,16 @@ constructor(       * The last location where this view was at before going to the desired location. This is useful       * for guided transitions.       */ -    @MediaLocation private var previousLocation = -1 +    @MediaLocation private var previousLocation = LOCATION_UNKNOWN      /** The desired location where the view will be at the end of the transition. */ -    @MediaLocation private var desiredLocation = -1 +    @MediaLocation private var desiredLocation = LOCATION_UNKNOWN      /**       * The current attachment location where the view is currently attached. Usually this matches       * the desired location except for animations whenever a view moves to the new desired location,       * during which it is in [IN_OVERLAY].       */ -    @MediaLocation private var currentAttachmentLocation = -1 +    @MediaLocation private var currentAttachmentLocation = LOCATION_UNKNOWN      private var inSplitShade = false @@ -627,7 +626,7 @@ constructor(                              secureSettings.getBoolForUser(                                  Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,                                  true, -                                UserHandle.USER_CURRENT +                                UserHandle.USER_CURRENT,                              )                      }                  } @@ -635,7 +634,7 @@ constructor(          secureSettings.registerContentObserverForUserSync(              Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,              settingsObserver, -            UserHandle.USER_ALL +            UserHandle.USER_ALL,          )          // Listen to the communal UI state. Make sure that communal UI is showing and hub itself is @@ -651,7 +650,7 @@ constructor(                      shadeInteractor.shadeExpansion                          .mapLatest { it < EXPANSION_THRESHOLD }                          .distinctUntilChanged(), -                    ::Triple +                    ::Triple,                  )                  .collectLatest { (communalShowing, isDreaming, isShadeExpanding) ->                      isCommunalShowing = communalShowing @@ -689,10 +688,10 @@ constructor(          if (mediaObject.location == desiredLocation) {              // In case we are overriding a view that is already visible, make sure we attach it              // to this new host view in the below call -            desiredLocation = -1 +            desiredLocation = LOCATION_UNKNOWN          }          if (mediaObject.location == currentAttachmentLocation) { -            currentAttachmentLocation = -1 +            currentAttachmentLocation = LOCATION_UNKNOWN          }          updateDesiredLocation()          return viewHost @@ -734,7 +733,7 @@ constructor(       */      private fun updateDesiredLocation(          forceNoAnimation: Boolean = false, -        forceStateUpdate: Boolean = false +        forceStateUpdate: Boolean = false,      ) =          traceSection("MediaHierarchyManager#updateDesiredLocation") {              val desiredLocation = calculateLocation() @@ -758,7 +757,7 @@ constructor(                          previousLocation = LOCATION_QQS                      }                  } -                val isNewView = this.desiredLocation == -1 +                val isNewView = this.desiredLocation == LOCATION_UNKNOWN                  this.desiredLocation = desiredLocation                  // Let's perform a transition                  val animate = @@ -774,7 +773,7 @@ constructor(                          host,                          animate,                          animDuration, -                        delay +                        delay,                      )                  }                  performTransitionToNewLocation(isNewView, animate) @@ -868,7 +867,7 @@ constructor(      private fun shouldAnimateTransition(          @MediaLocation currentLocation: Int, -        @MediaLocation previousLocation: Int +        @MediaLocation previousLocation: Int,      ): Boolean {          if (isCurrentlyInGuidedTransformation()) {              return false @@ -990,7 +989,7 @@ constructor(          startBounds: Rect,          endBounds: Rect,          progress: Float, -        result: Rect? = null +        result: Rect? = null,      ): Rect {          val left =              MathUtils.lerp(startBounds.left.toFloat(), endBounds.left.toFloat(), progress).toInt() @@ -1014,7 +1013,7 @@ constructor(      }      private fun hasValidStartAndEndLocations(): Boolean { -        return previousLocation != -1 && desiredLocation != -1 +        return previousLocation != LOCATION_UNKNOWN && desiredLocation != LOCATION_UNKNOWN      }      /** Calculate the transformation type for the current animation */ @@ -1099,21 +1098,21 @@ constructor(          bounds: Rect,          alpha: Float,          immediately: Boolean = false, -        clipBounds: Rect = EMPTY_RECT +        clipBounds: Rect = EMPTY_RECT,      ) =          traceSection("MediaHierarchyManager#applyState") {              currentBounds.set(bounds)              currentClipping = clipBounds              carouselAlpha = if (isCurrentlyFading()) alpha else 1.0f              val onlyUseEndState = !isCurrentlyInGuidedTransformation() || isCurrentlyFading() -            val startLocation = if (onlyUseEndState) -1 else previousLocation +            val startLocation = if (onlyUseEndState) LOCATION_UNKNOWN else previousLocation              val progress = if (onlyUseEndState) 1.0f else getTransformationProgress()              val endLocation = resolveLocationForFading()              mediaCarouselController.setCurrentState(                  startLocation,                  endLocation,                  progress, -                immediately +                immediately,              )              updateHostAttachment()              if (currentAttachmentLocation == IN_OVERLAY) { @@ -1125,7 +1124,7 @@ constructor(                      currentBounds.left,                      currentBounds.top,                      currentBounds.right, -                    currentBounds.bottom +                    currentBounds.bottom,                  )              }          } @@ -1186,7 +1185,7 @@ constructor(                      mediaCarouselController.onDesiredLocationChanged(                          newLocation,                          getHost(newLocation), -                        animate = false +                        animate = false,                      )                  }              } @@ -1201,7 +1200,7 @@ constructor(          if (isCrossFadeAnimatorRunning) {              // When animating between two hosts with a fade, let's keep ourselves in the old              // location for the first half, and then switch over to the end location -            if (animationCrossFadeProgress > 0.5 || previousLocation == -1) { +            if (animationCrossFadeProgress > 0.5 || previousLocation == LOCATION_UNKNOWN) {                  return crossFadeAnimationEndLocation              } else {                  return crossFadeAnimationStartLocation @@ -1364,6 +1363,9 @@ constructor(          /** Attached at the root of the hierarchy in an overlay */          const val IN_OVERLAY = -1000 +        /** Not attached to any view */ +        const val LOCATION_UNKNOWN = -1 +          /**           * The default transformation type where the hosts transform into each other using a direct           * transition @@ -1388,8 +1390,8 @@ private val EMPTY_RECT = Rect()      value =          [              MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION, -            MediaHierarchyManager.TRANSFORMATION_TYPE_FADE -        ] +            MediaHierarchyManager.TRANSFORMATION_TYPE_FADE, +        ],  )  @Retention(AnnotationRetention.SOURCE)  private annotation class TransformationType @@ -1403,7 +1405,8 @@ private annotation class TransformationType              MediaHierarchyManager.LOCATION_LOCKSCREEN,              MediaHierarchyManager.LOCATION_DREAM_OVERLAY,              MediaHierarchyManager.LOCATION_COMMUNAL_HUB, -        ] +            MediaHierarchyManager.LOCATION_UNKNOWN, +        ],  )  @Retention(AnnotationRetention.SOURCE)  annotation class MediaLocation diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt index e57de09f1063..3928a711f840 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt @@ -91,7 +91,7 @@ constructor(       */      enum class TYPE {          PLAYER, -        RECOMMENDATION +        RECOMMENDATION,      }      companion object { @@ -120,7 +120,7 @@ constructor(       * finished       */      @MediaLocation -    var currentEndLocation: Int = -1 +    var currentEndLocation: Int = MediaHierarchyManager.LOCATION_UNKNOWN          set(value) {              if (field != value) {                  field = value @@ -130,7 +130,7 @@ constructor(          }      /** The starting location of the view where it starts for all animations and transitions */ -    @MediaLocation private var currentStartLocation: Int = -1 +    @MediaLocation private var currentStartLocation: Int = MediaHierarchyManager.LOCATION_UNKNOWN      /** The progress of the transition or 1.0 if there is no transition happening */      private var currentTransitionProgress: Float = 1.0f @@ -294,14 +294,14 @@ constructor(          object : MediaHostStatesManager.Callback {              override fun onHostStateChanged(                  @MediaLocation location: Int, -                mediaHostState: MediaHostState +                mediaHostState: MediaHostState,              ) {                  if (location == currentEndLocation || location == currentStartLocation) {                      setCurrentState(                          currentStartLocation,                          currentEndLocation,                          currentTransitionProgress, -                        applyImmediately = false +                        applyImmediately = false,                      )                  }              } @@ -442,7 +442,7 @@ constructor(      /** Apply squishFraction to a copy of viewState such that the cached version is untouched. */      internal fun squishViewState(          viewState: TransitionViewState, -        squishFraction: Float +        squishFraction: Float,      ): TransitionViewState {          val squishedViewState = viewState.copy()          val squishedHeight = (squishedViewState.measureHeight * squishFraction).toInt() @@ -458,13 +458,13 @@ constructor(              MediaViewHolder.expandedBottomActionIds,              squishedViewState.measureHeight.toFloat(),              squishedViewState, -            squishFraction +            squishFraction,          )          calculateWidgetGroupAlphaForSquishiness(              MediaViewHolder.detailIds,              squishedViewState.measureHeight.toFloat(),              squishedViewState, -            squishFraction +            squishFraction,          )          // recommendation card          val titlesTop = @@ -472,13 +472,13 @@ constructor(                  RecommendationViewHolder.mediaTitlesAndSubtitlesIds,                  squishedViewState.measureHeight.toFloat(),                  squishedViewState, -                squishFraction +                squishFraction,              )          calculateWidgetGroupAlphaForSquishiness(              RecommendationViewHolder.mediaContainersIds,              titlesTop,              squishedViewState, -            squishFraction +            squishFraction,          )          return squishedViewState      } @@ -517,7 +517,7 @@ constructor(          widgetGroupIds: Set<Int>,          groupEndPosition: Float,          squishedViewState: TransitionViewState, -        squishFraction: Float +        squishFraction: Float,      ): Float {          val nonsquishedHeight = squishedViewState.measureHeight          var groupTop = squishedViewState.measureHeight.toFloat() @@ -547,7 +547,7 @@ constructor(                          calculateAlpha(                              squishFraction,                              startPosition / nonsquishedHeight, -                            endPosition / nonsquishedHeight +                            endPosition / nonsquishedHeight,                          )                  }              } @@ -562,10 +562,10 @@ constructor(      @VisibleForTesting      fun obtainViewState(          state: MediaHostState?, -        isGutsAnimation: Boolean = false +        isGutsAnimation: Boolean = false,      ): TransitionViewState? {          if (SceneContainerFlag.isEnabled) { -            return obtainSceneContainerViewState() +            return obtainSceneContainerViewState(state)          }          if (state == null || state.measurementInput == null) { @@ -606,7 +606,7 @@ constructor(                  transitionLayout!!.calculateViewState(                      state.measurementInput!!,                      constraintSetForExpansion(state.expansion), -                    TransitionViewState() +                    TransitionViewState(),                  )              setGutsViewState(result) @@ -653,7 +653,7 @@ constructor(              logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)              this.transitionLayout = transitionLayout              layoutController.attach(transitionLayout) -            if (currentEndLocation == -1) { +            if (currentEndLocation == MediaHierarchyManager.LOCATION_UNKNOWN) {                  return              }              // Set the previously set state immediately to the view, now that it's finally attached @@ -661,7 +661,7 @@ constructor(                  startLocation = currentStartLocation,                  endLocation = currentEndLocation,                  transitionProgress = currentTransitionProgress, -                applyImmediately = true +                applyImmediately = true,              )          } @@ -695,7 +695,7 @@ constructor(                  Interpolators.EMPHASIZED_DECELERATE,                  titleText,                  artistText, -                explicitIndicator +                explicitIndicator,              )          val exit =              loadAnimator( @@ -704,7 +704,7 @@ constructor(                  Interpolators.EMPHASIZED_ACCELERATE,                  titleText,                  artistText, -                explicitIndicator +                explicitIndicator,              )          metadataAnimationHandler = MetadataAnimationHandler(exit, enter) @@ -713,7 +713,7 @@ constructor(                  mediaCard.context,                  mediaViewHolder,                  multiRippleController, -                turbulenceNoiseController +                turbulenceNoiseController,              )          // For Turbulence noise. @@ -728,7 +728,7 @@ constructor(              object : LoadingEffect.AnimationStateChangedCallback {                  override fun onStateChanged(                      oldState: LoadingEffect.AnimationState, -                    newState: LoadingEffect.AnimationState +                    newState: LoadingEffect.AnimationState,                  ) {                      if (newState === LoadingEffect.AnimationState.NOT_PLAYING) {                          loadingEffectView.visibility = View.INVISIBLE @@ -755,12 +755,12 @@ constructor(                  MediaControlViewBinder.setVisibleAndAlpha(                      expandedLayout,                      it.scrubbingTotalTimeView.id, -                    isTimeVisible +                    isTimeVisible,                  )                  MediaControlViewBinder.setVisibleAndAlpha(                      expandedLayout,                      it.scrubbingElapsedTimeView.id, -                    isTimeVisible +                    isTimeVisible,                  )              } @@ -788,7 +788,7 @@ constructor(                          collapsedLayout,                          isButtonVisible,                          notVisibleValue, -                        showInCollapsed = true +                        showInCollapsed = true,                      )                  }              } @@ -822,7 +822,7 @@ constructor(                      createTurbulenceNoiseConfig(                          it.loadingEffectView,                          it.turbulenceNoiseView, -                        colorSchemeTransition +                        colorSchemeTransition,                      )              }              if (Flags.shaderlibLoadingEffectRefactor()) { @@ -832,23 +832,23 @@ constructor(                              TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,                              turbulenceNoiseAnimationConfig,                              noiseDrawCallback, -                            stateChangedCallback +                            stateChangedCallback,                          )                  }                  colorSchemeTransition.loadingEffect = loadingEffect                  loadingEffect.play()                  mainExecutor.executeDelayed(                      loadingEffect::finish, -                    MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION +                    MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION,                  )              } else {                  turbulenceNoiseController.play(                      TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE, -                    turbulenceNoiseAnimationConfig +                    turbulenceNoiseAnimationConfig,                  )                  mainExecutor.executeDelayed(                      turbulenceNoiseController::finish, -                    MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION +                    MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION,                  )              }          } @@ -920,7 +920,7 @@ constructor(                              startViewState,                              startHostState.disappearParameters,                              transitionProgress, -                            tmpState +                            tmpState,                          )                  }              } else if (startHostState != null && !startHostState.visible) { @@ -931,7 +931,7 @@ constructor(                          endViewState,                          endHostState.disappearParameters,                          1.0f - transitionProgress, -                        tmpState +                        tmpState,                      )              } else if (transitionProgress == 1.0f || startViewState == null) {                  // We're at the end. Let's use that state @@ -945,13 +945,13 @@ constructor(                          startViewState,                          endViewState,                          transitionProgress, -                        tmpState +                        tmpState,                      )              }              logger.logMediaSize(                  "setCurrentState (progress $transitionProgress)",                  result.width, -                result.height +                result.height,              )              layoutController.setState(                  result, @@ -966,7 +966,7 @@ constructor(      private fun updateViewStateSize(          viewState: TransitionViewState?,          location: Int, -        outState: TransitionViewState +        outState: TransitionViewState,      ): TransitionViewState? {          var result = viewState?.copy(outState) ?: return null          val state = mediaHostStatesManager.mediaHostStates[location] @@ -1020,15 +1020,19 @@ constructor(      }      /** Get a view state based on the width and height set by the scene */ -    private fun obtainSceneContainerViewState(): TransitionViewState? { +    private fun obtainSceneContainerViewState(state: MediaHostState?): TransitionViewState? {          logger.logMediaSize("scene container", widthInSceneContainerPx, heightInSceneContainerPx) +        if (state?.measurementInput == null) { +            return null +        } +          // Similar to obtainViewState: Let's create a new measurement          val result =              transitionLayout?.calculateViewState(                  MeasurementInput(widthInSceneContainerPx, heightInSceneContainerPx), -                expandedLayout, -                TransitionViewState() +                if (state.expansion > 0) expandedLayout else collapsedLayout, +                TransitionViewState(),              )          result?.let {              // And then ensure the guts visibility is set correctly @@ -1049,7 +1053,7 @@ constructor(      private fun obtainViewStateForLocation(@MediaLocation location: Int): TransitionViewState? {          val mediaHostState = mediaHostStatesManager.mediaHostStates[location] ?: return null          if (SceneContainerFlag.isEnabled) { -            return obtainSceneContainerViewState() +            return obtainSceneContainerViewState(mediaHostState)          }          val viewState = obtainViewState(mediaHostState) @@ -1080,9 +1084,10 @@ constructor(      fun refreshState() =          traceSection("MediaViewController#refreshState") {              if (SceneContainerFlag.isEnabled) { +                val hostState = mediaHostStatesManager.mediaHostStates[currentEndLocation]                  // 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 { +                obtainSceneContainerViewState(hostState)?.let {                      // Get scene container state, then setCurrentState                      layoutController.setState(                          state = it, @@ -1106,7 +1111,7 @@ constructor(                  currentStartLocation,                  currentEndLocation,                  currentTransitionProgress, -                applyImmediately = true +                applyImmediately = true,              )          } @@ -1115,7 +1120,7 @@ constructor(          context: Context,          animId: Int,          motionInterpolator: Interpolator?, -        vararg targets: View? +        vararg targets: View?,      ): AnimatorSet {          val animators = ArrayList<Animator>()          for (target in targets) { @@ -1132,7 +1137,7 @@ constructor(      private fun createTurbulenceNoiseConfig(          loadingEffectView: LoadingEffectView,          turbulenceNoiseView: TurbulenceNoiseView, -        colorSchemeTransition: ColorSchemeTransition +        colorSchemeTransition: ColorSchemeTransition,      ): TurbulenceNoiseAnimationConfig {          val targetView: View =              if (Flags.shaderlibLoadingEffectRefactor()) { @@ -1163,7 +1168,7 @@ constructor(              targetView.context.resources.displayMetrics.density,              lumaMatteBlendFactor = 0.26f,              lumaMatteOverallBrightness = 0.09f, -            shouldInverseNoiseLuminosity = false +            shouldInverseNoiseLuminosity = false,          )      } @@ -1185,5 +1190,5 @@ private data class CacheKey(      var widthMeasureSpec: Int = -1,      var heightMeasureSpec: Int = -1,      var expansion: Float = 0.0f, -    var gutsVisible: Boolean = false +    var gutsVisible: Boolean = false,  ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt index e7f7171d5be3..b2137afa05e6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt @@ -56,6 +56,9 @@ constructor(      private val mediaLogger: MediaLogger,  ) { +    val hasAnyMediaOrRecommendations: StateFlow<Boolean> = interactor.hasAnyMediaOrRecommendation +    val hasActiveMediaOrRecommendations: StateFlow<Boolean> = +        interactor.hasActiveMediaOrRecommendation      val mediaItems: StateFlow<List<MediaCommonViewModel>> =          interactor.currentMedia              .map { sortedItems -> @@ -114,7 +117,7 @@ constructor(          qsExpanded: Boolean,          visibleIndex: Int,          location: Int, -        isUpdate: Boolean = false +        isUpdate: Boolean = false,      ) {          // Skip logging if on LS or QQS, and there is no active media card          if (!qsExpanded && !interactor.hasActiveMediaOrRecommendation()) return @@ -127,7 +130,7 @@ constructor(          val instanceId = commonModel.mediaLoadedModel.instanceId          return mediaControlByInstanceId[instanceId]?.copy(              immediatelyUpdateUi = commonModel.mediaLoadedModel.immediatelyUpdateUi, -            updateTime = commonModel.updateTime +            updateTime = commonModel.updateTime,          )              ?: MediaCommonViewModel.MediaControl(                      instanceId = instanceId, @@ -144,7 +147,7 @@ constructor(                      },                      onUpdated = { onMediaControlAddedOrUpdated(it, commonModel) },                      isMediaFromRec = commonModel.isMediaFromRec, -                    updateTime = commonModel.updateTime +                    updateTime = commonModel.updateTime,                  )                  .also { mediaControlByInstanceId[instanceId] = it }      } @@ -165,7 +168,7 @@ constructor(          return mediaRecs?.copy(              key = commonModel.recsLoadingModel.key,              loadingEnabled = -                interactor.isRecommendationActive() || mediaFlags.isPersistentSsCardEnabled() +                interactor.isRecommendationActive() || mediaFlags.isPersistentSsCardEnabled(),          )              ?: MediaCommonViewModel.MediaRecommendations(                      key = commonModel.recsLoadingModel.key, @@ -195,7 +198,7 @@ constructor(      private fun onMediaControlAddedOrUpdated(          commonViewModel: MediaCommonViewModel, -        commonModel: MediaCommonModel.MediaControl +        commonModel: MediaCommonModel.MediaControl,      ) {          if (commonModel.canBeRemoved && !Utils.useMediaResumption(applicationContext)) {              // This media control is due for removal as it is now paused + timed out, and resumption @@ -222,7 +225,7 @@ constructor(      private fun onMediaRecommendationRemoved(          commonModel: MediaCommonModel.MediaRecommendations, -        immediatelyRemove: Boolean +        immediatelyRemove: Boolean,      ) {          mediaLogger.logMediaRecommendationCardRemoved(commonModel.recsLoadingModel.key)          if (immediatelyRemove || isReorderingAllowed()) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt index f07f2de08537..4173d2aa272e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt @@ -33,6 +33,8 @@ import com.android.systemui.media.controls.domain.pipeline.interactor.MediaContr  import com.android.systemui.media.controls.shared.model.MediaAction  import com.android.systemui.media.controls.shared.model.MediaButton  import com.android.systemui.media.controls.shared.model.MediaControlModel +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager +import com.android.systemui.media.controls.ui.controller.MediaLocation  import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_CLICK_EVENT  import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_DISMISS_EVENT  import com.android.systemui.media.controls.util.MediaUiEventLogger @@ -70,7 +72,7 @@ class MediaControlViewModel(      private var isPlaying = false      private var isAnyButtonClicked = false -    private var location = -1 +    @MediaLocation private var location = MediaHierarchyManager.LOCATION_UNKNOWN      private var playerViewModel: MediaPlayerViewModel? = null      fun isNewPlayer(viewModel: MediaPlayerViewModel): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt index a7bce7772270..6bc6b10a1dfd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt @@ -31,6 +31,8 @@ import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecom  import com.android.systemui.media.controls.shared.model.MediaRecModel  import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel  import com.android.systemui.media.controls.shared.model.NUM_REQUIRED_RECOMMENDATIONS +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager +import com.android.systemui.media.controls.ui.controller.MediaLocation  import com.android.systemui.media.controls.ui.controller.MediaViewController.Companion.GUTS_ANIMATION_DURATION  import com.android.systemui.media.controls.util.MediaDataUtils  import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_CLICK_EVENT @@ -66,7 +68,7 @@ constructor(              .distinctUntilChanged()              .flowOn(backgroundDispatcher) -    private var location = -1 +    @MediaLocation private var location = MediaHierarchyManager.LOCATION_UNKNOWN      /**       * Called whenever the recommendation has been expired or removed by the user. This method |