Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c8ab79dc authored by Michael Mikhail's avatar Michael Mikhail Committed by Android (Google) Code Review
Browse files

Merge changes If440c9b7,I80717bbd into main

* changes:
  Add unknown location to media locations
  [Flexiglass] add collapsed layout for UMO and handle transitions
parents 489039c3 d98f375a
Loading
Loading
Loading
Loading
+25 −2
Original line number Diff line number Diff line
@@ -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
}
+153 −0
Original line number Diff line number Diff line
/*
 * 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
                    }
                }
        }
    }
}
+2 −4
Original line number Diff line number Diff line
@@ -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)

+11 −8
Original line number Diff line number Diff line
@@ -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,
    )
}
+13 −3
Original line number Diff line number Diff line
@@ -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() }
            }
        }
    }
Loading