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

Commit 498166da authored by András Kurucz's avatar András Kurucz
Browse files

[Flexiglass] Don't allow scene changes by swipes while expanding notifs

When the user was dragging down on a notification in SingleShade,
and the stack was scrolled all the way to the top, at the end of
the expansion gesture the scene stack transitioned from Shade to
QuickSettings. The cure is to:
 - Dispatch touches to the scene framework, while it is expanding a
notification.
 - Create a nested scroller, that consumes all the drag amount while
the gesture is being used to expand a notification.
 - Rely on Modifier.disableSwipesWhenScrolling() to disable the scene
   change, as the previously added scroller has comsumed some of this
   gesture.
 - Cleanup the old mechanism of isCurrentGestureOverscroll(), as it
   was broken since an API was removed from the scene stack.

Bug: 416342077
Test: atest PlatformScenarioTests:android.platform.test.scenario.sysui.notification.DraggingToExpandGroupNotifications
Flag: com.android.systemui.scene_container

Change-Id: Idabdbf63dff7478e3d822236d1e2f789c9917df3
parent a4fc9ffb
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -42,7 +42,6 @@ fun NotificationScrimNestedScrollConnection(
    maxScrimOffset: Float,
    contentHeight: () -> Float,
    minVisibleScrimHeight: () -> Float,
    isCurrentGestureOverscroll: () -> Boolean,
    onStart: (Float) -> Unit = {},
    onStop: (Float) -> Unit = {},
    flingBehavior: FlingBehavior,
@@ -60,7 +59,7 @@ fun NotificationScrimNestedScrollConnection(
        // scrolling down and content is done scrolling to top. After that, the scrim
        // needs to collapse; collapse the scrim until it is at the maxScrimOffset.
        canStartPostScroll = { offsetAvailable, _, _ ->
            offsetAvailable > 0 && (scrimOffset() < maxScrimOffset || isCurrentGestureOverscroll())
            offsetAvailable > 0 && (scrimOffset() < maxScrimOffset)
        },
        onStart = { firstScroll ->
            onStart(firstScroll)
+46 −8
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@ import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
@@ -95,6 +96,9 @@ import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.gesture.effect.OffsetOverscrollEffect
import com.android.compose.gesture.effect.rememberOffsetOverscrollEffect
import com.android.compose.modifiers.thenIf
import com.android.compose.nestedscroll.OnStopScope
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import com.android.compose.nestedscroll.ScrollController
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
@@ -460,13 +464,7 @@ fun ContentScope.NotificationScrollingStack(
    }

    val scrimNestedScrollConnection =
        shadeSession.rememberSession(
            key = "ScrimConnection",
            scrimOffset,
            minScrimTop,
            viewModel.isCurrentGestureOverscroll,
            density,
        ) {
        shadeSession.rememberSession(key = "ScrimConnection", scrimOffset, minScrimTop, density) {
            val flingSpec: DecayAnimationSpec<Float> = splineBasedDecay(density)
            val flingBehavior = NotificationScrimFlingBehavior(flingSpec)
            NotificationScrimNestedScrollConnection(
@@ -479,11 +477,50 @@ fun ContentScope.NotificationScrollingStack(
                maxScrimOffset = 0f,
                contentHeight = { stackHeight.intValue.toFloat() },
                minVisibleScrimHeight = minVisibleScrimHeight,
                isCurrentGestureOverscroll = { viewModel.isCurrentGestureOverscroll },
                flingBehavior = flingBehavior,
            )
        }

    val swipeToExpandNotificationScrollConnection =
        shadeSession.rememberSession(
            key = "SwipeToExpandNotificationScrollConnection",
            scrimOffset,
            minScrimTop,
            density,
            viewModel.isCurrentGestureExpandingNotification,
        ) {
            PriorityNestedScrollConnection(
                orientation = Orientation.Vertical,
                canStartPreScroll = { _, _, _ -> false },
                canStartPostScroll = { _, _, _ -> viewModel.isCurrentGestureExpandingNotification },
                onStart = { firstScroll ->
                    object : ScrollController {
                        override fun onScroll(
                            deltaScroll: Float,
                            source: NestedScrollSource,
                        ): Float {
                            return if (viewModel.isCurrentGestureExpandingNotification) {
                                // consume all the amount, when this swipe is expanding a
                                // notification
                                deltaScroll
                            } else {
                                // don't consume anything, when the expansion is done
                                0f
                            }
                        }

                        override fun onCancel() {
                            // No-op
                        }

                        override fun canStopOnPreFling(): Boolean = false

                        override suspend fun OnStopScope.onStop(initialVelocity: Float): Float = 0f
                    }
                },
            )
        }

    val overScrollEffect: OffsetOverscrollEffect = rememberOffsetOverscrollEffect()
    // whether the stack is moving due to a swipe or fling
    val isScrollInProgress =
@@ -596,6 +633,7 @@ fun ContentScope.NotificationScrollingStack(
            Column(
                modifier =
                    Modifier.disableSwipesWhenScrolling()
                        .nestedScroll(swipeToExpandNotificationScrollConnection)
                        .thenIf(supportNestedScrolling) {
                            Modifier.nestedScroll(scrimNestedScrollConnection)
                        }
+0 −20
Original line number Diff line number Diff line
@@ -36,7 +36,6 @@ class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() {
    private var wasStarted = false
    private var scrimOffset = 0f
    private var contentHeight = 0f
    private var isCurrentGestureOverscroll = false
    private val customFlingBehavior =
        object : FlingBehavior {
            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
@@ -54,7 +53,6 @@ class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() {
            maxScrimOffset = MAX_SCRIM_OFFSET,
            contentHeight = { contentHeight },
            minVisibleScrimHeight = { MIN_VISIBLE_SCRIM_HEIGHT },
            isCurrentGestureOverscroll = { isCurrentGestureOverscroll },
            onStart = { isStarted = true },
            onStop = {
                wasStarted = true
@@ -183,24 +181,6 @@ class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() {
        assertThat(isStarted).isEqualTo(false)
    }

    @Test
    fun canStartPostScroll_externalOverscrollGesture_startButIgnoreScroll() = runTest {
        scrimOffset = MAX_SCRIM_OFFSET
        isCurrentGestureOverscroll = true

        val offsetConsumed =
            scrollConnection.onPostScroll(
                consumed = Offset.Zero,
                available = Offset(x = 0f, y = 1f),
                source = UserInput,
            )

        assertThat(offsetConsumed).isEqualTo(Offset.Zero)
        // Returning 0 offset will immediately stop the connection
        assertThat(wasStarted).isEqualTo(true)
        assertThat(isStarted).isEqualTo(false)
    }

    @Test
    fun canContinueScroll_inBetweenMinMaxOffset_true() = runTest {
        scrimOffset = (MIN_SCRIM_OFFSET + MAX_SCRIM_OFFSET) / 2f
+9 −17
Original line number Diff line number Diff line
@@ -1299,9 +1299,10 @@ public class NotificationStackScrollLayout
    }

    @Override
    public void setCurrentGestureOverscrollConsumer(@Nullable Consumer<Boolean> consumer) {
    public void setCurrentGestureExpandingNotificationConsumer(
            @Nullable Consumer<Boolean> consumer) {
        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
        mScrollViewFields.setCurrentGestureOverscrollConsumer(consumer);
        mScrollViewFields.setCurrentGestureExpandingNotificationConsumer(consumer);
    }

    @Override
@@ -3656,13 +3657,13 @@ public class NotificationStackScrollLayout
            if (action == MotionEvent.ACTION_DOWN && !isTouchInGuts) {
                mController.closeControlsDueToOutsideTouch();
            }
            if (mIsBeingDragged) {
            if (mIsBeingDragged || mExpandingNotification) {
                boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL;
                if (mSendingTouchesToSceneFramework) {
                    MotionEvent adjustedEvent = MotionEvent.obtain(ev);
                    adjustedEvent.setLocation(ev.getRawX(), ev.getRawY());
                    mScrollViewFields.sendCurrentGestureOverscroll(
                            getExpandedInThisMotion() && !isUpOrCancel);
                    mScrollViewFields.sendCurrentGestureExpandingNotification(
                            mExpandingNotification && !isUpOrCancel);
                    mController.sendTouchToSceneFramework(adjustedEvent);
                    adjustedEvent.recycle();
                } else if (!isUpOrCancel) {
@@ -3673,14 +3674,15 @@ public class NotificationStackScrollLayout
                    downEvent.setAction(MotionEvent.ACTION_DOWN);
                    downEvent.setLocation(ev.getRawX(), ev.getRawY());
                    mScrollViewFields.sendCurrentGestureInGuts(isTouchInGuts);
                    mScrollViewFields.sendCurrentGestureOverscroll(getExpandedInThisMotion());
                    mScrollViewFields.sendCurrentGestureExpandingNotification(
                            mExpandingNotification);
                    mController.sendTouchToSceneFramework(downEvent);
                    downEvent.recycle();
                }

                if (isUpOrCancel) {
                    mScrollViewFields.sendCurrentGestureInGuts(false);
                    mScrollViewFields.sendCurrentGestureOverscroll(false);
                    mScrollViewFields.sendCurrentGestureExpandingNotification(false);
                    setIsBeingDragged(false);
                }
            }
@@ -5872,11 +5874,6 @@ public class NotificationStackScrollLayout
        return mExpandingNotification;
    }

    @VisibleForTesting
    void setExpandingNotification(boolean isExpanding) {
        mExpandingNotification = isExpanding;
    }

    boolean getDisallowScrollingInThisMotion() {
        return mDisallowScrollingInThisMotion;
    }
@@ -5889,11 +5886,6 @@ public class NotificationStackScrollLayout
        return mExpandedInThisMotion;
    }

    @VisibleForTesting
    void setExpandedInThisMotion(boolean expandedInThisMotion) {
        mExpandedInThisMotion = expandedInThisMotion;
    }

    boolean getDisallowDismissInThisMotion() {
        return mDisallowDismissInThisMotion;
    }
+6 −5
Original line number Diff line number Diff line
@@ -62,9 +62,10 @@ class ScrollViewFields {

    /**
     * When a gesture is consumed internally by NSSL but needs to be handled by other elements (such
     * as the notif scrim) as overscroll, we can notify the placeholder through here.
     * as the notif scrim), we can notify the placeholder through here.
     */
    var currentGestureOverscrollConsumer: Consumer<Boolean>? = null
    var currentGestureExpandingNotificationConsumer: Consumer<Boolean>? = null

    /**
     * When a gesture is on open notification guts, which means scene container should not close the
     * guts off of this gesture, we can notify the placeholder through here.
@@ -81,9 +82,9 @@ class ScrollViewFields {
    fun sendSyntheticScroll(syntheticScroll: Float) =
        syntheticScrollConsumer?.accept(syntheticScroll)

    /** send [isCurrentGestureOverscroll] to the [currentGestureOverscrollConsumer], if present. */
    fun sendCurrentGestureOverscroll(isCurrentGestureOverscroll: Boolean) =
        currentGestureOverscrollConsumer?.accept(isCurrentGestureOverscroll)
    /** send [isExpanding] to the [currentGestureExpandingNotificationConsumer], if present. */
    fun sendCurrentGestureExpandingNotification(isExpanding: Boolean) =
        currentGestureExpandingNotificationConsumer?.accept(isExpanding)

    /** send [isCurrentGestureInGuts] to the [currentGestureInGutsConsumer], if present. */
    fun sendCurrentGestureInGuts(isCurrentGestureInGuts: Boolean) =
Loading