Merge "[bc25] Fix notifications shade rendering over lockscreen in Dual Shade." into main
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 2745f6e..4c6834c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -47,7 +47,6 @@
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -60,7 +59,6 @@
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
@@ -70,7 +68,6 @@
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
@@ -82,6 +79,7 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.NestedScrollBehavior
@@ -93,8 +91,9 @@
import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.session.ui.composable.rememberSession
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
@@ -112,18 +111,16 @@
val NotificationStackPlaceholder = ElementKey("NotificationStackPlaceholder")
val HeadsUpNotificationPlaceholder =
ElementKey("HeadsUpNotificationPlaceholder", contentPicker = LowestZIndexContentPicker)
- val ShelfSpace = ElementKey("ShelfSpace")
val NotificationStackCutoffGuideline = ElementKey("NotificationStackCutoffGuideline")
}
-
- // Expansion fraction thresholds (between 0-1f) at which the corresponding value should be
- // at its maximum, given they are at their minimum value at expansion = 0f.
- object TransitionThresholds {
- const val EXPANSION_FOR_MAX_CORNER_RADIUS = 0.1f
- const val EXPANSION_FOR_MAX_SCRIM_ALPHA = 0.3f
- }
}
+private val notificationsShadeContentKey: ContentKey
+ get() = if (DualShade.isEnabled) Overlays.NotificationsShade else Scenes.Shade
+
+private val quickSettingsShadeContentKey: ContentKey
+ get() = if (DualShade.isEnabled) Overlays.QuickSettingsShade else Scenes.QuickSettings
+
/**
* Adds the space where heads up notifications can appear in the scene. This should generally be the
* entire size of the scene.
@@ -146,7 +143,7 @@
// This element is sometimes opted out of the shared element system, so there
// can be multiple instances of it during a transition. Thus we need to
// determine which instance should feed its bounds to NSSL to avoid providing
- // conflicting values
+ // conflicting values.
val useBounds = useHunBounds()
if (useBounds) {
val positionInWindow = coordinates.positionInWindow()
@@ -157,8 +154,8 @@
" bounds=$boundsInWindow"
}
// Note: boundsInWindow doesn't scroll off the screen, so use
- // positionInWindow
- // for top bound, which can scroll off screen while snoozing
+ // positionInWindow for top bound, which can scroll off screen while
+ // snoozing.
stackScrollView.setHeadsUpTop(positionInWindow.y)
stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
}
@@ -285,7 +282,8 @@
shouldFillMaxSize: Boolean = true,
shouldReserveSpaceForNavBar: Boolean = true,
shouldIncludeHeadsUpSpace: Boolean = true,
- shadeMode: ShadeMode,
+ shouldShowScrim: Boolean = true,
+ supportNestedScrolling: Boolean,
onEmptySpaceClick: (() -> Unit)? = null,
modifier: Modifier = Modifier,
) {
@@ -293,6 +291,7 @@
val density = LocalDensity.current
val screenCornerRadius = LocalScreenCornerRadius.current
val scrimCornerRadius = dimensionResource(R.dimen.notification_scrim_corner_radius)
+ val scrimBackgroundColor = MaterialTheme.colorScheme.surface
val scrollState =
shadeSession.rememberSaveableSession(saver = ScrollState.Saver, key = null) {
ScrollState(initial = 0)
@@ -427,8 +426,14 @@
// completes.
if (
scrimOffset.value < 0 &&
- layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Gone) ||
- layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
+ (layoutState.isTransitioning(
+ from = notificationsShadeContentKey,
+ to = Scenes.Gone,
+ ) ||
+ layoutState.isTransitioning(
+ from = notificationsShadeContentKey,
+ to = Scenes.Lockscreen,
+ ))
) {
IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
} else if (
@@ -498,7 +503,7 @@
(expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
} else 1f
}
- .background(MaterialTheme.colorScheme.surface)
+ .thenIf(shouldShowScrim) { Modifier.background(scrimBackgroundColor) }
.thenIf(shouldFillMaxSize) { Modifier.fillMaxSize() }
.debugBackground(viewModel, DEBUG_BOX_COLOR)
) {
@@ -508,7 +513,7 @@
topBehavior = NestedScrollBehavior.EdgeWithPreview,
isExternalOverscrollGesture = { isCurrentGestureOverscroll.value },
)
- .thenIf(shadeMode == ShadeMode.Single) {
+ .thenIf(supportNestedScrolling) {
Modifier.nestedScroll(scrimNestedScrollConnection)
}
.stackVerticalOverscroll(coroutineScope) { scrollState.canScrollForward }
@@ -550,38 +555,6 @@
}
/**
- * This may be added to the lockscreen to provide a space to the start of the lock icon where the
- * short shelf has room to flow vertically below the lock icon, but to its start, allowing more
- * notifications to fit in the stack itself. (see: b/213934746)
- *
- * NOTE: this is totally unused for now; it is here to clarify the future plan
- */
-@Composable
-fun SceneScope.NotificationShelfSpace(
- viewModel: NotificationsPlaceholderViewModel,
- modifier: Modifier = Modifier,
-) {
- Text(
- text = "Shelf Space",
- modifier
- .element(key = Notifications.Elements.ShelfSpace)
- .fillMaxWidth()
- .onPlaced { coordinates: LayoutCoordinates ->
- debugLog(viewModel) {
- ("SHELF onPlaced:" +
- " size=${coordinates.size}" +
- " bounds=${coordinates.boundsInWindow()}")
- }
- }
- .clip(RoundedCornerShape(24.dp))
- .background(MaterialTheme.colorScheme.primaryContainer)
- .padding(16.dp),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.onPrimaryContainer,
- )
-}
-
-/**
* A 0 height horizontal spacer to be placed at the bottom-most position in the current scene, where
* the notification contents (stack, footer, shelf) should be drawn.
*/
@@ -673,15 +646,19 @@
}
}
+private fun TransitionState.isOnLockscreen(): Boolean {
+ return currentScene == Scenes.Lockscreen && currentOverlays.isEmpty()
+}
+
private fun shouldUseLockscreenStackBounds(state: TransitionState): Boolean {
- return state is TransitionState.Idle && state.currentScene == Scenes.Lockscreen
+ return state is TransitionState.Idle && state.isOnLockscreen()
}
private fun shouldUseLockscreenHunBounds(state: TransitionState): Boolean {
return when (state) {
- is TransitionState.Idle -> state.currentScene == Scenes.Lockscreen
+ is TransitionState.Idle -> state.isOnLockscreen()
is TransitionState.Transition ->
- state.isTransitioning(from = Scenes.QuickSettings, to = Scenes.Lockscreen)
+ state.isTransitioning(from = quickSettingsShadeContentKey, to = Scenes.Lockscreen)
}
}
@@ -690,7 +667,7 @@
shouldPunchHoleBehindScrim: Boolean,
): Boolean {
return shouldPunchHoleBehindScrim ||
- state.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
+ state.isTransitioning(from = notificationsShadeContentKey, to = Scenes.Lockscreen)
}
private fun calculateCornerRadius(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
index a22becc..5b99670 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -33,7 +33,6 @@
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.composable.Overlay
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -69,9 +68,7 @@
}
@Composable
- override fun ContentScope.Content(
- modifier: Modifier,
- ) {
+ override fun ContentScope.Content(modifier: Modifier) {
val viewModel =
rememberViewModel("NotificationsShadeOverlay-viewModel") {
contentViewModelFactory.create()
@@ -81,10 +78,7 @@
viewModel.notificationsPlaceholderViewModelFactory.create()
}
- OverlayShade(
- modifier = modifier,
- onScrimClicked = viewModel::onScrimClicked,
- ) {
+ OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) {
Column {
ExpandedShadeHeader(
viewModelFactory = viewModel.shadeHeaderViewModelFactory,
@@ -102,7 +96,8 @@
shouldPunchHoleBehindScrim = false,
shouldFillMaxSize = false,
shouldReserveSpaceForNavBar = false,
- shadeMode = ShadeMode.Dual,
+ shouldShowScrim = false,
+ supportNestedScrolling = false,
modifier = Modifier.fillMaxWidth(),
)
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 6304979..e27f46b 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
@@ -100,7 +100,6 @@
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Scene
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.Shade
@@ -114,11 +113,9 @@
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
/** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class QuickSettingsScene
@Inject
@@ -427,7 +424,7 @@
maxScrimTop = { screenHeight },
shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
shouldIncludeHeadsUpSpace = false,
- shadeMode = ShadeMode.Single,
+ supportNestedScrolling = true,
modifier =
Modifier.fillMaxWidth()
.offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index 337f53a5..23c4f12 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -21,26 +21,20 @@
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
import kotlin.time.Duration.Companion.milliseconds
-fun TransitionBuilder.toNotificationsShadeTransition(
- durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
swipeSpec =
spring(
stiffness = Spring.StiffnessMediumLow,
visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
)
- distance = UserActionDistance { fromSceneSize, orientation ->
- fromSceneSize.height.toFloat() * 2 / 3f
- }
-
+ scaleSize(OverlayShade.Elements.Panel, height = 0f)
translate(OverlayShade.Elements.Panel, Edge.Top)
fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
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 c6c42fc..3ec057b 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
@@ -40,7 +40,6 @@
import androidx.compose.foundation.layout.padding
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.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -89,7 +88,6 @@
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.view.MediaHost
-import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.COLLAPSED
import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.EXPANDED
import com.android.systemui.media.dagger.MediaModule.QS_PANEL
@@ -117,7 +115,6 @@
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
object Shade {
@@ -128,23 +125,13 @@
}
object Dimensions {
- val ScrimCornerSize = 32.dp
val HorizontalPadding = 16.dp
val ScrimOverscrollLimit = 32.dp
const val ScrimVisibilityThreshold = 5f
}
-
- object Shapes {
- val Scrim =
- RoundedCornerShape(
- topStart = Dimensions.ScrimCornerSize,
- topEnd = Dimensions.ScrimCornerSize,
- )
- }
}
/** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class ShadeScene
@Inject
@@ -197,11 +184,11 @@
)
init {
- qqsMediaHost.expansion = MediaHostState.EXPANDED
+ qqsMediaHost.expansion = EXPANDED
qqsMediaHost.showsOnlyActiveMedia = true
qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
- qsMediaHost.expansion = MediaHostState.EXPANDED
+ qsMediaHost.expansion = EXPANDED
qsMediaHost.showsOnlyActiveMedia = false
qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
}
@@ -329,8 +316,7 @@
modifier =
modifier.thenIf(shouldPunchHoleBehindScrim) {
// Render the scene to an offscreen buffer so that BlendMode.DstOut only clears this
- // scene
- // (and not the one under it) during a scene transition.
+ // scene (and not the one under it) during a scene transition.
Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
}
) {
@@ -382,8 +368,8 @@
stackScrollView = notificationStackScrollView,
viewModel = notificationsPlaceholderViewModel,
maxScrimTop = { maxNotifScrimTop.toFloat() },
- shadeMode = ShadeMode.Single,
shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
+ supportNestedScrolling = true,
onEmptySpaceClick =
viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
modifier =
@@ -601,7 +587,7 @@
maxScrimTop = { 0f },
shouldPunchHoleBehindScrim = false,
shouldReserveSpaceForNavBar = false,
- shadeMode = ShadeMode.Split,
+ supportNestedScrolling = false,
onEmptySpaceClick =
viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
modifier =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index c9eaec7..aec81b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -84,9 +84,9 @@
private fun fullyExpandedDuringSceneChange(change: ChangeScene): Boolean {
// The lockscreen stack is visible during all transitions away from the lockscreen, so keep
// the stack expanded until those transitions finish.
- return if (change.isFrom({ it == Scenes.Lockscreen }, to = { true })) {
+ return if (change.isTransitioning(from = Scenes.Lockscreen)) {
true
- } else if (change.isFrom({ it == Scenes.Shade }, to = { it == Scenes.Lockscreen })) {
+ } else if (change.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)) {
false
} else {
(expandedInScene(change.fromScene) && expandedInScene(change.toScene))
@@ -101,11 +101,11 @@
return if (fullyExpandedDuringSceneChange(change)) {
1f
} else if (
- change.isBetween({ it == Scenes.Gone }, { it == Scenes.Shade }) ||
- change.isFrom({ it == Scenes.Shade }, to = { it == Scenes.Lockscreen })
+ change.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
+ change.isTransitioning(from = Scenes.Gone, to = Scenes.Lockscreen)
) {
shadeExpansion
- } else if (change.isBetween({ it == Scenes.Gone }, { it == Scenes.QuickSettings })) {
+ } else if (change.isTransitioningBetween(Scenes.Gone, Scenes.QuickSettings)) {
// during QS expansion, increase fraction at same rate as scrim alpha,
// but start when scrim alpha is at EXPANSION_FOR_DELAYED_STACK_FADE_IN.
(qsExpansion / EXPANSION_FOR_MAX_SCRIM_ALPHA - EXPANSION_FOR_DELAYED_STACK_FADE_IN)
@@ -213,7 +213,11 @@
private val qsAllowsClipping: Flow<Boolean> =
combine(shadeInteractor.shadeMode, shadeInteractor.qsExpansion) { shadeMode, qsExpansion ->
- qsExpansion < 0.5f || shadeMode != ShadeMode.Single
+ when (shadeMode) {
+ is ShadeMode.Dual -> false
+ is ShadeMode.Split -> true
+ is ShadeMode.Single -> qsExpansion < 0.5f
+ }
}
.distinctUntilChanged()
@@ -325,9 +329,3 @@
fun create(): NotificationScrollViewModel
}
}
-
-private fun ChangeScene.isBetween(a: (SceneKey) -> Boolean, b: (SceneKey) -> Boolean): Boolean =
- (a(fromScene) && b(toScene)) || (b(fromScene) && a(toScene))
-
-private fun ChangeScene.isFrom(from: (SceneKey) -> Boolean, to: (SceneKey) -> Boolean): Boolean =
- from(fromScene) && to(toScene)