diff options
12 files changed, 227 insertions, 31 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 26ab10b459d8..85d5d6a41965 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 @@ -24,9 +24,11 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.approachLayout import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource +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 @@ -51,6 +53,7 @@ fun SceneScope.MediaCarousel( mediaHost: MediaHost, modifier: Modifier = Modifier, carouselController: MediaCarouselController, + offsetProvider: (() -> IntOffset)? = null, ) { if (!isVisible) { return @@ -68,21 +71,38 @@ fun SceneScope.MediaCarousel( MovableElement( key = MediaCarousel.Elements.Content, - modifier = modifier.height(mediaHeight).fillMaxWidth() + modifier = modifier.height(mediaHeight).fillMaxWidth(), ) { content { AndroidView( modifier = - Modifier.fillMaxSize().layout { measurable, constraints -> - val placeable = measurable.measure(constraints) + 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) + // 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) } - }, + layout(placeable.width, placeable.height) { + placeable.placeRelative(0, 0) + } + }, factory = { context -> FrameLayout(context).apply { layoutParams = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt index 3f04f3728bef..70c0db1582c4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt @@ -27,7 +27,6 @@ import com.android.systemui.scene.shared.model.Scenes /** [ElementContentPicker] implementation for the media carousel object. */ object MediaContentPicker : StaticElementContentPicker { - const val SHADE_FRACTION = 0.66f override val contents = setOf( Scenes.Lockscreen, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt index a9da733116fc..71fa6c9e567a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt @@ -25,7 +25,6 @@ import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance import com.android.compose.animation.scene.UserActionDistanceScope import com.android.systemui.media.controls.ui.composable.MediaCarousel -import com.android.systemui.media.controls.ui.composable.MediaContentPicker import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.shade.ui.composable.Shade @@ -54,13 +53,7 @@ fun TransitionBuilder.goneToSplitShadeTransition( fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) } fractionRange(start = .33f) { - val qsTranslation = - ShadeHeader.Dimensions.CollapsedHeight * MediaContentPicker.SHADE_FRACTION - val qsExpansionDiff = - ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight - translate(MediaCarousel.Elements.Content, y = -(qsExpansionDiff + qsTranslation)) fade(MediaCarousel.Elements.Content) - fade(ShadeHeader.Elements.Clock) fade(ShadeHeader.Elements.CollapsedContentStart) fade(ShadeHeader.Elements.CollapsedContentEnd) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt index 21dfc49cbe7b..b677dff2dcf9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt @@ -26,7 +26,6 @@ import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance import com.android.compose.animation.scene.UserActionDistanceScope import com.android.systemui.media.controls.ui.composable.MediaCarousel -import com.android.systemui.media.controls.ui.composable.MediaContentPicker import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.scene.shared.model.Scenes @@ -62,12 +61,9 @@ fun TransitionBuilder.toShadeTransition( fade(QuickSettings.Elements.FooterActions) } - val qsTranslation = ShadeHeader.Dimensions.CollapsedHeight * MediaContentPicker.SHADE_FRACTION - val qsExpansionDiff = - ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight - - translate(QuickSettings.Elements.QuickQuickSettings, y = -qsTranslation) - translate(MediaCarousel.Elements.Content, y = -(qsExpansionDiff + qsTranslation)) + val qsTranslation = -ShadeHeader.Dimensions.CollapsedHeight * 0.66f + translate(QuickSettings.Elements.QuickQuickSettings, y = qsTranslation) + translate(MediaCarousel.Elements.Content, y = qsTranslation) translate(Notifications.Elements.NotificationScrim, Edge.Top, false) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeMediaOffsetProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeMediaOffsetProvider.kt new file mode 100644 index 000000000000..e0b7909dcaa3 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeMediaOffsetProvider.kt @@ -0,0 +1,79 @@ +/* + * 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.shade.ui.composable + +import androidx.compose.ui.unit.IntOffset +import com.android.systemui.qs.ui.adapter.QSSceneAdapter + +/** + * Provider for the extra offset for the Media section in the shade to accommodate for the squishing + * qs or qqs tiles. + */ +interface ShadeMediaOffsetProvider { + + /** Returns current offset to be applied to the Media Carousel */ + val offset: IntOffset + + /** + * [ShadeMediaOffsetProvider] implementation for Quick Settings. + * + * [updateLayout] should represent an access to some state to trigger Compose to relayout to + * track [QSSceneAdapter] internal state changes during the transition. + */ + class Qs(private val updateLayout: () -> Unit, private val qsSceneAdapter: QSSceneAdapter) : + ShadeMediaOffsetProvider { + + override val offset: IntOffset + get() = + calculateQsOffset( + updateLayout, + qsSceneAdapter.qsHeight, + qsSceneAdapter.squishedQsHeight + ) + } + + /** + * [ShadeMediaOffsetProvider] implementation for Quick Quick Settings. + * + * [updateLayout] should represent an access to some state to trigger Compose to relayout to + * track [QSSceneAdapter] internal state changes during the transition. + */ + class Qqs(private val updateLayout: () -> Unit, private val qsSceneAdapter: QSSceneAdapter) : + ShadeMediaOffsetProvider { + + override val offset: IntOffset + get() = + calculateQsOffset( + updateLayout, + qsSceneAdapter.qqsHeight, + qsSceneAdapter.squishedQqsHeight + ) + } + + companion object { + + protected fun calculateQsOffset( + updateLayout: () -> Unit, + qsHeight: Int, + qsSquishedHeight: Int + ): IntOffset { + updateLayout() + val distanceFromBottomToActualBottom = qsHeight - qsSquishedHeight + return IntOffset(0, -distanceFromBottomToActualBottom) + } + } +} 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 0e3fcf4598af..16972bc95e57 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 @@ -118,7 +118,6 @@ import kotlinx.coroutines.flow.Flow object Shade { object Elements { - val MediaCarousel = ElementKey("ShadeMediaCarousel") val BackgroundScrim = ElementKey("ShadeBackgroundScrim", contentPicker = LowestZIndexContentPicker) val SplitShadeStartColumn = ElementKey("SplitShadeStartColumn") @@ -283,6 +282,13 @@ private fun SceneScope.SingleShade( val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding() + val mediaOffsetProvider = remember { + ShadeMediaOffsetProvider.Qqs( + { @Suppress("UNUSED_EXPRESSION") tileSquishiness }, + viewModel.qsSceneAdapter, + ) + } + Box( modifier = modifier.thenIf(shouldPunchHoleBehindScrim) { @@ -335,12 +341,12 @@ private fun SceneScope.SingleShade( ) } - MediaCarousel( + ShadeMediaCarousel( isVisible = isMediaVisible, mediaHost = mediaHost, + mediaOffsetProvider = mediaOffsetProvider, modifier = - Modifier.fillMaxWidth() - .layoutId(QSMediaMeasurePolicy.LayoutId.Media), + Modifier.layoutId(QSMediaMeasurePolicy.LayoutId.Media), carouselController = mediaCarouselController, ) } @@ -497,6 +503,13 @@ private fun SceneScope.SplitShade( val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha } + val mediaOffsetProvider = remember { + ShadeMediaOffsetProvider.Qs( + { @Suppress("UNUSED_EXPRESSION") tileSquishiness }, + viewModel.qsSceneAdapter, + ) + } + Box { Box( modifier = @@ -570,11 +583,13 @@ private fun SceneScope.SplitShade( squishiness = { tileSquishiness }, ) } - MediaCarousel( + + ShadeMediaCarousel( isVisible = isMediaVisible, mediaHost = mediaHost, + mediaOffsetProvider = mediaOffsetProvider, modifier = - Modifier.fillMaxWidth().thenIf( + Modifier.thenIf( MediaContentPicker.shouldElevateMedia(layoutState) ) { Modifier.zIndex(1f) @@ -620,3 +635,25 @@ private fun SceneScope.SplitShade( ) } } + +@Composable +private fun SceneScope.ShadeMediaCarousel( + isVisible: Boolean, + mediaHost: MediaHost, + carouselController: MediaCarouselController, + mediaOffsetProvider: ShadeMediaOffsetProvider, + modifier: Modifier = Modifier, +) { + MediaCarousel( + modifier = modifier.fillMaxWidth(), + isVisible = isVisible, + mediaHost = mediaHost, + carouselController = carouselController, + offsetProvider = + if (MediaContentPicker.shouldElevateMedia(layoutState)) { + null + } else { + { mediaOffsetProvider.offset } + } + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 9939075b77d2..1511f31a3f92 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -259,6 +259,13 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { } /** + * @return height with the squishiness fraction applied. + */ + int getSquishedQqsHeight() { + return mHeader.getSquishedHeight(); + } + + /** * Returns the size of QS (or the QSCustomizer), regardless of the measured size of this view * @return size in pixels of QS (or QSCustomizer) */ @@ -267,6 +274,13 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { : mQSPanel.getMeasuredHeight(); } + /** + * @return height with the squishiness fraction applied. + */ + int getSquishedQsHeight() { + return mQSPanel.getSquishedHeight(); + } + public void setExpansion(float expansion) { mQsExpansion = expansion; if (mQSPanelContainer != null) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index a6fd35a9ee37..0b37b5b7be3d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -992,11 +992,25 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl return mContainer.getQqsHeight(); } + /** + * @return height with the squishiness fraction applied. + */ + public int getSquishedQqsHeight() { + return mContainer.getSquishedQqsHeight(); + } + public int getQSHeight() { return mContainer.getQsHeight(); } /** + * @return height with the squishiness fraction applied. + */ + public int getSquishedQsHeight() { + return mContainer.getSquishedQsHeight(); + } + + /** * Pass the size of the navbar when it's at the bottom of the device so it can be used as * padding * @param padding size of the bottom nav bar in px diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 032891fa715e..d3bed27ab2ab 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -733,6 +733,30 @@ public class QSPanel extends LinearLayout implements Tunable { mCanCollapse = canCollapse; } + /** + * @return height with the {@link QSPanel#setSquishinessFraction(float)} applied. + */ + public int getSquishedHeight() { + if (mFooter != null) { + final ViewGroup.LayoutParams footerLayoutParams = mFooter.getLayoutParams(); + final int footerBottomMargin; + if (footerLayoutParams instanceof MarginLayoutParams) { + footerBottomMargin = ((MarginLayoutParams) footerLayoutParams).bottomMargin; + } else { + footerBottomMargin = 0; + } + // This is the distance between the top of the QSPanel and the last view in the + // layout (which is the effective the bottom) + return mFooter.getBottom() + footerBottomMargin - getTop(); + } + if (mTileLayout != null) { + // Footer absence means that the panel is in the QQS. In this case it's just height + // of the tiles + paddings. + return mTileLayout.getTilesHeight() + getPaddingBottom() + getPaddingTop(); + } + return getHeight(); + } + @Nullable @VisibleForTesting View getMediaPlaceholder() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 5a3f1c0b7426..8fde52c910da 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -123,4 +123,11 @@ public class QuickStatusBarHeader extends FrameLayout { lp.setMarginEnd(marginEnd); view.setLayoutParams(lp); } + + /** + * @return height with the squishiness fraction applied. + */ + public int getSquishedHeight() { + return mHeaderQsPanel.getSquishedHeight(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt index ae2f32aae874..dfcf21628c3b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt @@ -34,7 +34,6 @@ import com.android.systemui.plugins.qs.QSContainerController import com.android.systemui.qs.QSContainerImpl import com.android.systemui.qs.QSImpl import com.android.systemui.qs.dagger.QSSceneComponent -import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel.state import com.android.systemui.res.R import com.android.systemui.settings.brightness.MirrorController import com.android.systemui.shade.domain.interactor.ShadeInteractor @@ -126,12 +125,18 @@ interface QSSceneAdapter { /** The current height of QQS in the current [qsView], or 0 if there's no view. */ val qqsHeight: Int + /** @return height with the squishiness fraction applied. */ + val squishedQqsHeight: Int + /** * The current height of QS in the current [qsView], or 0 if there's no view. If customizing, it * will return the height allocated to the customizer. */ val qsHeight: Int + /** @return height with the squishiness fraction applied. */ + val squishedQsHeight: Int + /** Compatibility for use by LockscreenShadeTransitionController. Matches default from [QS] */ val isQsFullyCollapsed: Boolean get() = true @@ -273,9 +278,15 @@ constructor( override val qqsHeight: Int get() = qsImpl.value?.qqsHeight ?: 0 + override val squishedQqsHeight: Int + get() = qsImpl.value?.squishedQqsHeight ?: 0 + override val qsHeight: Int get() = qsImpl.value?.qsHeight ?: 0 + override val squishedQsHeight: Int + get() = qsImpl.value?.squishedQsHeight ?: 0 + // If value is null, there's no QS and therefore it's fully collapsed. override val isQsFullyCollapsed: Boolean get() = qsImpl.value?.isFullyCollapsed ?: true diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt index b6194e3f511d..bbe753e8c7a5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt @@ -27,7 +27,9 @@ import kotlinx.coroutines.flow.filterNotNull class FakeQSSceneAdapter( private val inflateDelegate: suspend (Context) -> View, override val qqsHeight: Int = 0, + override val squishedQqsHeight: Int = 0, override val qsHeight: Int = 0, + override val squishedQsHeight: Int = 0, ) : QSSceneAdapter { private val _customizerState = MutableStateFlow<CustomizerState>(CustomizerState.Hidden) |