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

Commit e65540df authored by Bryce Lee's avatar Bryce Lee
Browse files

Allow Full Screen Glanceable Hub Swipe Entry on Lockscreen.

This changelist adds support for swiping in Glanceable Hub. When
enabled, horizontal swipe behaviors over the lockscreen under the
notification stack will drive the entry gesture into Glanceable
Hub.

Test: atest GlanceableHubContainerControllerTest#fullScreenSwipeGesture_doNotProcessTouchesInNotificationStack
Test atest MultiPointerDraggableTest#multiPointerSwipeDetectorInteraction
Flag: com.android.systemui.glanceable_hub_fullscreen_swipe
Bug: 339665673

Change-Id: Ib5d012e39b3ac4ea555b5cbb39c06cfc3c137fd9
parent fd5cf919
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -996,6 +996,13 @@ flag {
  }
}

flag {
  name: "glanceable_hub_fullscreen_swipe"
  namespace: "systemui"
  description: "Increase swipe area for gestures to bring in glanceable hub"
  bug: "339665673"
}

flag {
  name: "glanceable_hub_shortcut_button"
  namespace: "systemui"
+21 −4
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.CommunalSwipeDetector
import com.android.compose.animation.scene.DefaultSwipeDetector
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
@@ -35,6 +37,7 @@ import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
import com.android.systemui.Flags
import com.android.systemui.Flags.glanceableHubFullscreenSwipe
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.communal.ui.compose.extensions.allowGestures
@@ -108,6 +111,8 @@ fun CommunalContainer(
        )
    }

    val detector = remember { CommunalSwipeDetector() }

    DisposableEffect(state) {
        val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
        dataSourceDelegator.setDelegate(dataSource)
@@ -121,13 +126,25 @@ fun CommunalContainer(
        onDispose { viewModel.setTransitionState(null) }
    }

    val swipeSourceDetector =
        if (glanceableHubFullscreenSwipe()) {
            detector
        } else {
            FixedSizeEdgeDetector(dimensionResource(id = R.dimen.communal_gesture_initiation_width))
        }

    val swipeDetector =
        if (glanceableHubFullscreenSwipe()) {
            detector
        } else {
            DefaultSwipeDetector
        }

    SceneTransitionLayout(
        state = state,
        modifier = modifier.fillMaxSize(),
        swipeSourceDetector =
            FixedSizeEdgeDetector(
                dimensionResource(id = R.dimen.communal_gesture_initiation_width)
            ),
        swipeSourceDetector = swipeSourceDetector,
        swipeDetector = swipeDetector,
    ) {
        scene(
            CommunalScenes.Blank,
+56 −0
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.compose.animation.scene

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import kotlin.math.abs

private const val TRAVEL_RATIO_THRESHOLD = .5f

/**
 * {@link CommunalSwipeDetector} provides an implementation of {@link SwipeDetector} and {@link
 * SwipeSourceDetector} to enable fullscreen swipe handling to transition to and from the glanceable
 * hub.
 */
class CommunalSwipeDetector(private var lastDirection: SwipeSource? = null) :
    SwipeSourceDetector, SwipeDetector {
    override fun source(
        layoutSize: IntSize,
        position: IntOffset,
        density: Density,
        orientation: Orientation
    ): SwipeSource? {
        return lastDirection
    }

    override fun detectSwipe(change: PointerInputChange): Boolean {
        if (change.positionChange().x > 0) {
            lastDirection = Edge.Left
        } else {
            lastDirection = Edge.Right
        }

        // Determine whether the ratio of the distance traveled horizontally to the distance
        // traveled vertically exceeds the threshold.
        return abs(change.positionChange().x / change.positionChange().y) > TRAVEL_RATIO_THRESHOLD
    }
}
+13 −3
Original line number Diff line number Diff line
@@ -72,6 +72,7 @@ internal fun Modifier.multiPointerDraggable(
    enabled: () -> Boolean,
    startDragImmediately: (startedPosition: Offset) -> Boolean,
    onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
    swipeDetector: SwipeDetector = DefaultSwipeDetector,
): Modifier =
    this.then(
        MultiPointerDraggableElement(
@@ -79,6 +80,7 @@ internal fun Modifier.multiPointerDraggable(
            enabled,
            startDragImmediately,
            onDragStarted,
            swipeDetector,
        )
    )

@@ -88,6 +90,7 @@ private data class MultiPointerDraggableElement(
    private val startDragImmediately: (startedPosition: Offset) -> Boolean,
    private val onDragStarted:
        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
    private val swipeDetector: SwipeDetector,
) : ModifierNodeElement<MultiPointerDraggableNode>() {
    override fun create(): MultiPointerDraggableNode =
        MultiPointerDraggableNode(
@@ -95,6 +98,7 @@ private data class MultiPointerDraggableElement(
            enabled = enabled,
            startDragImmediately = startDragImmediately,
            onDragStarted = onDragStarted,
            swipeDetector = swipeDetector,
        )

    override fun update(node: MultiPointerDraggableNode) {
@@ -102,6 +106,7 @@ private data class MultiPointerDraggableElement(
        node.enabled = enabled
        node.startDragImmediately = startDragImmediately
        node.onDragStarted = onDragStarted
        node.swipeDetector = swipeDetector
    }
}

@@ -111,6 +116,7 @@ internal class MultiPointerDraggableNode(
    var startDragImmediately: (startedPosition: Offset) -> Boolean,
    var onDragStarted:
        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
    var swipeDetector: SwipeDetector = DefaultSwipeDetector,
) :
    PointerInputModifierNode,
    DelegatingNode(),
@@ -199,6 +205,7 @@ internal class MultiPointerDraggableNode(
                            onDragCancel = { controller ->
                                controller.onStop(velocity = 0f, canChangeScene = true)
                            },
                            swipeDetector = swipeDetector
                        )
                    } catch (exception: CancellationException) {
                        // If the coroutine scope is active, we can just restart the drag cycle.
@@ -226,7 +233,8 @@ internal class MultiPointerDraggableNode(
            (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
        onDrag: (controller: DragController, change: PointerInputChange, dragAmount: Float) -> Unit,
        onDragEnd: (controller: DragController) -> Unit,
        onDragCancel: (controller: DragController) -> Unit
        onDragCancel: (controller: DragController) -> Unit,
        swipeDetector: SwipeDetector,
    ) {
        // Wait for a consumable event in [PointerEventPass.Main] pass
        val consumablePointer = awaitConsumableEvent().changes.first()
@@ -238,9 +246,11 @@ internal class MultiPointerDraggableNode(
                consumablePointer
            } else {
                val onSlopReached = { change: PointerInputChange, over: Float ->
                    if (swipeDetector.detectSwipe(change)) {
                        change.consume()
                        overSlop = over
                    }
                }

                // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once it
                // is public.
+6 −1
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ fun SceneTransitionLayout(
    state: SceneTransitionLayoutState,
    modifier: Modifier = Modifier,
    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
    swipeDetector: SwipeDetector = DefaultSwipeDetector,
    @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
    scenes: SceneTransitionLayoutScope.() -> Unit,
) {
@@ -62,6 +63,7 @@ fun SceneTransitionLayout(
        state,
        modifier,
        swipeSourceDetector,
        swipeDetector,
        transitionInterceptionThreshold,
        onLayoutImpl = null,
        scenes,
@@ -95,6 +97,7 @@ fun SceneTransitionLayout(
    transitions: SceneTransitions,
    modifier: Modifier = Modifier,
    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
    swipeDetector: SwipeDetector = DefaultSwipeDetector,
    @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
    enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
    scenes: SceneTransitionLayoutScope.() -> Unit,
@@ -111,6 +114,7 @@ fun SceneTransitionLayout(
        state,
        modifier,
        swipeSourceDetector,
        swipeDetector,
        transitionInterceptionThreshold,
        scenes,
    )
@@ -467,6 +471,7 @@ internal fun SceneTransitionLayoutForTesting(
    state: SceneTransitionLayoutState,
    modifier: Modifier = Modifier,
    swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
    swipeDetector: SwipeDetector = DefaultSwipeDetector,
    transitionInterceptionThreshold: Float = 0f,
    onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
    scenes: SceneTransitionLayoutScope.() -> Unit,
@@ -502,5 +507,5 @@ internal fun SceneTransitionLayoutForTesting(
        layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
    }

    layoutImpl.Content(modifier)
    layoutImpl.Content(modifier, swipeDetector)
}
Loading