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

Commit 1646edae authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[bc25] Fix notifications shade rendering over lockscreen in Dual Shade." into main

parents 92228715 99695eb1
Loading
Loading
Loading
Loading
+33 −56
Original line number Diff line number Diff line
@@ -47,7 +47,6 @@ import androidx.compose.foundation.layout.windowInsetsBottomHeight
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.setValue
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.input.nestedscroll.nestedScroll
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.Velocity
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.common.ui.compose.windowinsets.LocalScreenCornerRadi
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 @@ object Notifications {
        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 @@ fun SceneScope.HeadsUpNotificationSpace(
                    // 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 @@ fun SceneScope.HeadsUpNotificationSpace(
                                " 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 @@ fun SceneScope.NotificationScrollingStack(
    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 @@ fun SceneScope.NotificationScrollingStack(
    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 @@ fun SceneScope.NotificationScrollingStack(
                    // 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 @@ fun SceneScope.NotificationScrollingStack(
                                (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 @@ fun SceneScope.NotificationScrollingStack(
                            topBehavior = NestedScrollBehavior.EdgeWithPreview,
                            isExternalOverscrollGesture = { isCurrentGestureOverscroll.value },
                        )
                        .thenIf(shadeMode == ShadeMode.Single) {
                        .thenIf(supportNestedScrolling) {
                            Modifier.nestedScroll(scrimNestedScrollConnection)
                        }
                        .stackVerticalOverscroll(coroutineScope) { scrollState.canScrollForward }
@@ -549,38 +554,6 @@ fun SceneScope.NotificationScrollingStack(
    }
}

/**
 * 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 suspend fun scrollNotificationStack(
    }
}

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 @@ private fun shouldAnimateScrimCornerRadius(
    shouldPunchHoleBehindScrim: Boolean,
): Boolean {
    return shouldPunchHoleBehindScrim ||
        state.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
        state.isTransitioning(from = notificationsShadeContentKey, to = Scenes.Lockscreen)
}

private fun calculateCornerRadius(
+4 −9
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlay
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 @@ constructor(
    }

    @Composable
    override fun ContentScope.Content(
        modifier: Modifier,
    ) {
    override fun ContentScope.Content(modifier: Modifier) {
        val viewModel =
            rememberViewModel("NotificationsShadeOverlay-viewModel") {
                contentViewModelFactory.create()
@@ -81,10 +78,7 @@ constructor(
                viewModel.notificationsPlaceholderViewModelFactory.create()
            }

        OverlayShade(
            modifier = modifier,
            onScrimClicked = viewModel::onScrimClicked,
        ) {
        OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) {
            Column {
                ExpandedShadeHeader(
                    viewModelFactory = viewModel.shadeHeaderViewModelFactory,
@@ -102,7 +96,8 @@ constructor(
                    shouldPunchHoleBehindScrim = false,
                    shouldFillMaxSize = false,
                    shouldReserveSpaceForNavBar = false,
                    shadeMode = ShadeMode.Dual,
                    shouldShowScrim = false,
                    supportNestedScrolling = false,
                    modifier = Modifier.fillMaxWidth(),
                )

+1 −4
Original line number Diff line number Diff line
@@ -100,7 +100,6 @@ import com.android.systemui.res.R
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 dagger.Lazy
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 @@ private fun SceneScope.QuickSettingsScene(
            maxScrimTop = { screenHeight },
            shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
            shouldIncludeHeadsUpSpace = false,
            shadeMode = ShadeMode.Single,
            supportNestedScrolling = true,
            modifier =
                Modifier.fillMaxWidth()
                    .offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }
+2 −8
Original line number Diff line number Diff line
@@ -21,26 +21,20 @@ import androidx.compose.animation.core.spring
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) }
+5 −19
Original line number Diff line number Diff line
@@ -40,7 +40,6 @@ import androidx.compose.foundation.layout.navigationBarsPadding
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.composable.shouldElevateMedia
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 dagger.Lazy
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 Shade {
    }

    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 @@ constructor(
        )

    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 @@ private fun SceneScope.SingleShade(
        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 @@ private fun SceneScope.SingleShade(
                    stackScrollView = notificationStackScrollView,
                    viewModel = notificationsPlaceholderViewModel,
                    maxScrimTop = { maxNotifScrimTop.toFloat() },
                    shadeMode = ShadeMode.Single,
                    shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
                    supportNestedScrolling = true,
                    onEmptySpaceClick =
                        viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
                    modifier =
@@ -601,7 +587,7 @@ private fun SceneScope.SplitShade(
                    maxScrimTop = { 0f },
                    shouldPunchHoleBehindScrim = false,
                    shouldReserveSpaceForNavBar = false,
                    shadeMode = ShadeMode.Split,
                    supportNestedScrolling = false,
                    onEmptySpaceClick =
                        viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
                    modifier =
Loading