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

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

Merge "[Flexiglass] Remove mediaCarousel android view composable" into main

parents 9df790c1 3d7fb3d0
Loading
Loading
Loading
Loading
+1 −45
Original line number Diff line number Diff line
@@ -24,18 +24,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import com.android.compose.animation.scene.ContentScope
import com.android.systemui.Flags.mediaControlsInCompose
import com.android.systemui.keyguard.ui.composable.elements.LockscreenUpperRegionElementProvider.Companion.LayoutType
import com.android.systemui.keyguard.ui.composable.elements.LockscreenUpperRegionElementProvider.Companion.getLayoutType
import com.android.systemui.keyguard.ui.viewmodel.KeyguardMediaViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
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
import com.android.systemui.media.remedia.ui.compose.Media
import com.android.systemui.media.remedia.ui.compose.MediaPresentationStyle
import com.android.systemui.plugins.keyguard.VRectF
import com.android.systemui.plugins.keyguard.ui.composable.elements.LockscreenElement
import com.android.systemui.plugins.keyguard.ui.composable.elements.LockscreenElementContext
import com.android.systemui.plugins.keyguard.ui.composable.elements.LockscreenElementFactory
@@ -44,20 +38,15 @@ import com.android.systemui.plugins.keyguard.ui.composable.elements.LockscreenEl
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
import javax.inject.Named
import kotlin.collections.List

class MediaElementProvider
@Inject
constructor(
    @ShadeDisplayAware private val context: Context,
    private val mediaCarouselController: MediaCarouselController,
    @param:Named(MediaModule.KEYGUARD) private val mediaHost: MediaHost,
    private val mediaViewModelFactory: KeyguardMediaViewModel.Factory,
) : LockscreenElementProvider {
    override val elements: List<LockscreenElement> by lazy {
        listOf(if (mediaControlsInCompose()) mediaCarouselElement else mediaCarouselLegacyElement)
    }
    override val elements: List<LockscreenElement> by lazy { listOf(mediaCarouselElement) }

    private val mediaCarouselElement =
        object : LockscreenElement {
@@ -95,37 +84,4 @@ constructor(
                }
            }
        }

    private val mediaCarouselLegacyElement =
        object : LockscreenElement {
            override val key = LockscreenElementKeys.MediaCarousel
            override val context = this@MediaElementProvider.context

            @Composable
            override fun ContentScope.LockscreenElement(
                factory: LockscreenElementFactory,
                context: LockscreenElementContext,
            ) {
                val horizontalPadding =
                    when (getLayoutType()) {
                        LayoutType.WIDE -> dimensionResource(R.dimen.notification_side_paddings)
                        LayoutType.NARROW ->
                            dimensionResource(R.dimen.notification_side_paddings) +
                                dimensionResource(R.dimen.notification_panel_margin_horizontal)
                    }

                val viewModel =
                    rememberViewModel("MediaCarouselElement") { mediaViewModelFactory.create() }

                AnimatedVisibility(viewModel.isMediaVisible) {
                    MediaCarousel(
                        isVisible = true,
                        mediaHost = mediaHost,
                        modifier = Modifier.fillMaxWidth().padding(horizontal = horizontalPadding),
                        carouselController = mediaCarouselController,
                        onReleaseCallback = { context.onElementPositioned(key, VRectF.ZERO) },
                    )
                }
            }
        }
}
+0 −147
Original line number Diff line number Diff line
/*
 * 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 android.view.View
import android.view.ViewGroup
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
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.MovableElementKey
import com.android.compose.windowsizeclass.LocalWindowSizeClass
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

object MediaCarousel {
    object Elements {
        internal val Content =
            MovableElementKey(
                debugName = "MediaCarouselContent",
                contentPicker = MediaContentPicker,
            )
    }
}

@Composable
fun ContentScope.MediaCarousel(
    isVisible: Boolean,
    mediaHost: MediaHost,
    modifier: Modifier = Modifier,
    carouselController: MediaCarouselController,
    offsetProvider: (() -> IntOffset)? = null,
    usingCollapsedLandscapeMedia: Boolean = false,
    isInSplitShade: Boolean = false,
    onReleaseCallback: (() -> Unit)? = null,
) {
    if (!isVisible || carouselController.isLockedAndHidden()) {
        return
    }
    val carouselState = remember { { stateForMediaCarouselContent(isInSplitShade) } }
    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,
        modifier = modifier.height(mediaHeight).fillMaxWidth(),
    ) {
        content {
            AndroidView(
                modifier =
                    Modifier.fillMaxSize()
                        .approachLayout(
                            isMeasurementApproachInProgress = { offsetProvider != null },
                            approachMeasure = { measurable, constraints ->
                                val placeable = measurable.measure(constraints)
                                layout(placeable.width, placeable.height) {
                                    placeable.placeRelative(
                                        offsetProvider?.invoke() ?: IntOffset.Zero
                                    )
                                }
                            },
                        )
                        .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)
                            carouselController.setSceneContainerSize(
                                placeable.width,
                                placeable.height,
                            )

                            layout(placeable.width, placeable.height) {
                                placeable.placeRelative(0, 0)
                            }
                        },
                factory = { context ->
                    MediaCarouselStateLoader.loadCarouselState(carouselController, carouselState())
                    FrameLayout(context).apply {
                        layoutParams =
                            FrameLayout.LayoutParams(
                                FrameLayout.LayoutParams.MATCH_PARENT,
                                FrameLayout.LayoutParams.MATCH_PARENT,
                            )
                    }
                },
                update = {
                    MediaCarouselStateLoader.loadCarouselState(carouselController, carouselState())
                    carouselController.mediaCarouselScrollHandler.showsSettingsButton =
                        !mediaHost.showsOnlyActiveMedia
                    it.setView(carouselController.mediaFrame)
                },
                onRelease = {
                    onReleaseCallback?.invoke()
                    it.removeAllViews()
                },
            )
        }
    }
}

private fun ViewGroup.setView(view: View) {
    if (view.parent == this) {
        return
    }
    (view.parent as? ViewGroup)?.removeView(view)
    addView(view)
}

@Composable
fun ContentScope.isLandscape(): Boolean {
    return LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact
}
+0 −160
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.ContentScope
import com.android.compose.animation.scene.SceneKey
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, isSplitShade: Boolean): Int {
        return when (scene) {
            Scenes.QuickSettings -> MediaHierarchyManager.LOCATION_QS
            Scenes.Shade -> {
                if (isSplitShade) MediaHierarchyManager.LOCATION_QS
                else 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 ContentScope.stateForMediaCarouselContent(isInSplitShade: Boolean): State {
        return when (val transitionState = layoutState.transitionState) {
            is TransitionState.Idle -> {
                if (MediaContentPicker.contents.contains(transitionState.currentScene)) {
                    State.Idle(getMediaLocation(transitionState.currentScene, isInSplitShade))
                } 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, isInSplitShade),
                            getMediaLocation(toScene, isInSplitShade),
                        )
                    } else if (MediaContentPicker.contents.contains(toScene)) {
                        State.InProgress(
                            transitionProgress = 1.0F,
                            startLocation = MediaHierarchyManager.LOCATION_UNKNOWN,
                            getMediaLocation(toScene, isInSplitShade),
                        )
                    } 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
                    }
                }
        }
    }
}
+0 −84
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.ElementContentPicker
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneTransitionLayoutState
import com.android.compose.animation.scene.StaticElementContentPicker
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes

/** [ElementContentPicker] implementation for the media carousel object. */
object MediaContentPicker : StaticElementContentPicker {

    override val contents =
        setOf(
            Overlays.NotificationsShade,
            Overlays.QuickSettingsShade,
            Scenes.Lockscreen,
            Scenes.Shade,
            Scenes.QuickSettings,
            Scenes.Communal,
        )

    override fun contentDuringTransition(
        element: ElementKey,
        transition: TransitionState.Transition,
        fromContentZIndex: Long,
        toContentZIndex: Long,
    ): ContentKey {
        return when {
            transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade) -> {
                Scenes.Shade
            }
            transition.isTransitioningBetween(Scenes.Lockscreen, Overlays.NotificationsShade) -> {
                Overlays.NotificationsShade
            }
            transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Communal) -> {
                Scenes.Lockscreen
            }
            transition.isTransitioningBetween(Scenes.QuickSettings, Scenes.Shade) -> {
                Scenes.QuickSettings
            }
            transition.isTransitioningBetween(
                Overlays.QuickSettingsShade,
                Overlays.NotificationsShade,
            ) -> {
                Overlays.QuickSettingsShade
            }
            transition.toContent in contents -> transition.toContent
            else -> {
                check(transition.fromContent in contents) {
                    "Media player should not be composed for the transition from " +
                        "${transition.fromContent} to ${transition.toContent}"
                }
                transition.fromContent
            }
        }
    }
}

/** Whether media should be laid on top of the rest for the given [transition]. */
fun MediaContentPicker.shouldElevateMedia(layoutState: SceneTransitionLayoutState): Boolean {
    return layoutState.currentTransition?.let { transition ->
        transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade) ||
            transition.isTransitioningBetween(Scenes.Lockscreen, Overlays.NotificationsShade)
    } ?: false
}
+2 −2
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.remedia.ui.compose.Media
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.shared.ui.QuickSettings
import com.android.systemui.shade.ui.composable.Shade
@@ -36,7 +36,7 @@ fun TransitionBuilder.goneToSplitShadeTransition(durationScale: Double = 1.0) {
    fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }

    fractionRange(start = .33f) {
        fade(MediaCarousel.Elements.Content)
        fade(Media.Elements.mediaCarousel)
        fade(ShadeHeader.Elements.Clock)
        fade(ShadeHeader.Elements.CollapsedContentStart)
        fade(ShadeHeader.Elements.CollapsedContentEnd)