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

Commit ea1f8243 authored by Michal Brzezinski's avatar Michal Brzezinski
Browse files

Calculating gesture progress for all touchpad gestures

This also required adding extra helper to tests.
Passing progress value to animation is blocked for now because AnimatedContent wouldn't work well with that. Will be enabled in follow-up CLs.

Part of adding live gesture tracking to gesture tutorial.

Bug: 369817369
Test: all respective tests for gesture
Flag: com.android.systemui.shared.new_touchpad_gestures_tutorial
Change-Id: I17c58d103e760aaf74124d2f000f07bd348c4f94
parent d891b4f5
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -60,6 +60,26 @@ class BackGestureRecognizerTest : SysuiTestCase() {
        )
    }

    @Test
    fun triggersProgressRelativeToDistance() {
        assertProgressWhileMovingFingers(deltaX = -SWIPE_DISTANCE / 2, expectedProgress = 0.5f)
        assertProgressWhileMovingFingers(deltaX = SWIPE_DISTANCE / 2, expectedProgress = 0.5f)
        assertProgressWhileMovingFingers(deltaX = -SWIPE_DISTANCE, expectedProgress = 1f)
        assertProgressWhileMovingFingers(deltaX = SWIPE_DISTANCE, expectedProgress = 1f)
    }

    private fun assertProgressWhileMovingFingers(deltaX: Float, expectedProgress: Float) {
        assertStateAfterEvents(
            events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) },
            expectedState = InProgress(progress = expectedProgress),
        )
    }

    @Test
    fun triggeredProgressIsNoBiggerThanOne() {
        assertProgressWhileMovingFingers(deltaX = SWIPE_DISTANCE * 2, expectedProgress = 1f)
    }

    @Test
    fun doesntTriggerGestureFinished_onGestureDistanceTooShort() {
        assertStateAfterEvents(
+2 −1
Original line number Diff line number Diff line
@@ -104,7 +104,8 @@ class EasterEggGestureTest : SysuiTestCase() {
    }

    private fun assertStateAfterTwoFingerGesture(gesturePath: List<Point>, wasTriggered: Boolean) {
        val events = TwoFingerGesture.createEvents { gesturePath.forEach { (x, y) -> move(x, y) } }
        val events =
            TwoFingerGesture.eventsForFullGesture { gesturePath.forEach { (x, y) -> move(x, y) } }
        assertStateAfterEvents(events = events, wasTriggered = wasTriggered)
    }

+21 −0
Original line number Diff line number Diff line
@@ -55,6 +55,27 @@ class HomeGestureRecognizerTest : SysuiTestCase() {
        )
    }

    @Test
    fun triggersProgressRelativeToDistance() {
        assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE / 2, expectedProgress = 0.5f)
        assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE, expectedProgress = 1f)
    }

    private fun assertProgressWhileMovingFingers(deltaY: Float, expectedProgress: Float) {
        assertStateAfterEvents(
            events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaY = deltaY) },
            expectedState = InProgress(progress = expectedProgress),
        )
    }

    @Test
    fun triggeredProgressIsBetweenZeroAndOne() {
        // going in the wrong direction
        assertProgressWhileMovingFingers(deltaY = SWIPE_DISTANCE / 2, expectedProgress = 0f)
        // going further than required distance
        assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE * 2, expectedProgress = 1f)
    }

    @Test
    fun doesntTriggerGestureFinished_onGestureDistanceTooShort() {
        assertStateAfterEvents(
+22 −1
Original line number Diff line number Diff line
@@ -77,10 +77,31 @@ class RecentAppsGestureRecognizerTest : SysuiTestCase() {
    fun triggersGestureProgressForThreeFingerGestureStarted() {
        assertStateAfterEvents(
            events = ThreeFingerGesture.startEvents(x = 0f, y = 0f),
            expectedState = InProgress(),
            expectedState = InProgress(progress = 0f),
        )
    }

    @Test
    fun triggersProgressRelativeToDistance() {
        assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE / 2, expectedProgress = 0.5f)
        assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE, expectedProgress = 1f)
    }

    private fun assertProgressWhileMovingFingers(deltaY: Float, expectedProgress: Float) {
        assertStateAfterEvents(
            events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaY = deltaY) },
            expectedState = InProgress(progress = expectedProgress),
        )
    }

    @Test
    fun triggeredProgressIsBetweenZeroAndOne() {
        // going in the wrong direction
        assertProgressWhileMovingFingers(deltaY = SWIPE_DISTANCE / 2, expectedProgress = 0f)
        // going further than required distance
        assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE * 2, expectedProgress = 1f)
    }

    @Test
    fun doesntTriggerGestureFinished_onGestureDistanceTooShort() {
        assertStateAfterEvents(
+99 −41
Original line number Diff line number Diff line
@@ -25,11 +25,23 @@ import android.view.MotionEvent.ACTION_UP
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.DEFAULT_X
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.DEFAULT_Y

/** Given gesture move events can build list of [MotionEvent]s included in that gesture */
interface GestureEventsBuilder {
    /**
 * Interface for gesture builders which support creating list of [MotionEvent] for common swipe
 * gestures. For simple usage see swipe* methods or use [createEvents] for more specific scenarios.
     * Creates full gesture including provided move events. This means returned events include DOWN,
     * MOVE and UP. Note that move event's x and y is always relative to the starting one.
     */
interface MultiFingerGesture {
    fun eventsForFullGesture(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent>

    /**
     * Creates partial gesture including provided move events. This means returned events include
     * DOWN and MOVE. Note that move event's x and y is always relative to the starting one.
     */
    fun eventsForGestureInProgress(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent>
}

/** Support creating list of [MotionEvent] for common swipe gestures. */
interface MultiFingerGesture : GestureEventsBuilder {

    companion object {
        const val SWIPE_DISTANCE = 100f
@@ -37,27 +49,41 @@ interface MultiFingerGesture {
        const val DEFAULT_Y = 500f
    }

    fun swipeUp(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaY = -distancePx) }

    fun swipeDown(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaY = distancePx) }
    fun swipeUp(distancePx: Float = SWIPE_DISTANCE) = eventsForFullGesture {
        move(deltaY = -distancePx)
    }

    fun swipeRight(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaX = distancePx) }
    fun swipeDown(distancePx: Float = SWIPE_DISTANCE) = eventsForFullGesture {
        move(deltaY = distancePx)
    }

    fun swipeLeft(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaX = -distancePx) }
    fun swipeRight(distancePx: Float = SWIPE_DISTANCE) = eventsForFullGesture {
        move(deltaX = distancePx)
    }

    /**
     * Creates gesture with provided move events. Note that move event's x and y is always relative
     * to the starting one
     */
    fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent>
    fun swipeLeft(distancePx: Float = SWIPE_DISTANCE) = eventsForFullGesture {
        move(deltaX = -distancePx)
    }
}

object ThreeFingerGesture : MultiFingerGesture {
    override fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent> {
        return touchpadGesture(

    private val moveEventsBuilder = MoveEventsBuilder(::threeFingerEvent)

    override fun eventsForFullGesture(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent> {
        return buildGesture(
            startEvents = { x, y -> startEvents(x, y) },
            moveEvents = moveEventsBuilder.getEvents(moveEvents),
            endEvents = { x, y -> endEvents(x, y) },
        )
    }

    override fun eventsForGestureInProgress(
        moveEvents: MoveEventsBuilder.() -> Unit
    ): List<MotionEvent> {
        return buildGesture(
            startEvents = { x, y -> startEvents(x, y) },
            moveEvents = GestureBuilder(::threeFingerEvent).apply { moveEvents() }.events,
            endEvents = { x, y -> endEvents(x, y) }
            moveEvents = moveEventsBuilder.getEvents(moveEvents),
        )
    }

@@ -65,7 +91,7 @@ object ThreeFingerGesture : MultiFingerGesture {
        return listOf(
            threeFingerEvent(ACTION_DOWN, x, y),
            threeFingerEvent(ACTION_POINTER_DOWN, x, y),
            threeFingerEvent(ACTION_POINTER_DOWN, x, y)
            threeFingerEvent(ACTION_POINTER_DOWN, x, y),
        )
    }

@@ -73,32 +99,43 @@ object ThreeFingerGesture : MultiFingerGesture {
        return listOf(
            threeFingerEvent(ACTION_POINTER_UP, x, y),
            threeFingerEvent(ACTION_POINTER_UP, x, y),
            threeFingerEvent(ACTION_UP, x, y)
            threeFingerEvent(ACTION_UP, x, y),
        )
    }

    private fun threeFingerEvent(
        action: Int,
        x: Float = DEFAULT_X,
        y: Float = DEFAULT_Y
        y: Float = DEFAULT_Y,
    ): MotionEvent {
        return touchpadEvent(
            action = action,
            x = x,
            y = y,
            classification = MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE,
            axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 3f)
            axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 3f),
        )
    }
}

object FourFingerGesture : MultiFingerGesture {

    override fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent> {
        return touchpadGesture(
    private val moveEventsBuilder = MoveEventsBuilder(::fourFingerEvent)

    override fun eventsForFullGesture(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent> {
        return buildGesture(
            startEvents = { x, y -> startEvents(x, y) },
            moveEvents = moveEventsBuilder.getEvents(moveEvents),
            endEvents = { x, y -> endEvents(x, y) },
        )
    }

    override fun eventsForGestureInProgress(
        moveEvents: MoveEventsBuilder.() -> Unit
    ): List<MotionEvent> {
        return buildGesture(
            startEvents = { x, y -> startEvents(x, y) },
            moveEvents = GestureBuilder(::fourFingerEvent).apply { moveEvents() }.events,
            endEvents = { x, y -> endEvents(x, y) }
            moveEvents = moveEventsBuilder.getEvents(moveEvents),
        )
    }

@@ -107,7 +144,7 @@ object FourFingerGesture : MultiFingerGesture {
            fourFingerEvent(ACTION_DOWN, x, y),
            fourFingerEvent(ACTION_POINTER_DOWN, x, y),
            fourFingerEvent(ACTION_POINTER_DOWN, x, y),
            fourFingerEvent(ACTION_POINTER_DOWN, x, y)
            fourFingerEvent(ACTION_POINTER_DOWN, x, y),
        )
    }

@@ -116,61 +153,74 @@ object FourFingerGesture : MultiFingerGesture {
            fourFingerEvent(ACTION_POINTER_UP, x, y),
            fourFingerEvent(ACTION_POINTER_UP, x, y),
            fourFingerEvent(ACTION_POINTER_UP, x, y),
            fourFingerEvent(ACTION_UP, x, y)
            fourFingerEvent(ACTION_UP, x, y),
        )
    }

    private fun fourFingerEvent(
        action: Int,
        x: Float = DEFAULT_X,
        y: Float = DEFAULT_Y
        y: Float = DEFAULT_Y,
    ): MotionEvent {
        return touchpadEvent(
            action = action,
            x = x,
            y = y,
            classification = MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE,
            axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 4f)
            axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 4f),
        )
    }
}

object TwoFingerGesture : MultiFingerGesture {

    override fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent> {
        return touchpadGesture(
            startEvents = { x, y -> listOf(twoFingerEvent(ACTION_DOWN, x, y)) },
            moveEvents = GestureBuilder(::twoFingerEvent).apply { moveEvents() }.events,
            endEvents = { x, y -> listOf(twoFingerEvent(ACTION_UP, x, y)) }
    private val moveEventsBuilder = MoveEventsBuilder(::twoFingerEvent)

    override fun eventsForFullGesture(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent> {
        return buildGesture(
            startEvents = { x, y -> startEvents(x, y) },
            moveEvents = moveEventsBuilder.getEvents(moveEvents),
            endEvents = { x, y -> listOf(twoFingerEvent(ACTION_UP, x, y)) },
        )
    }

    override fun eventsForGestureInProgress(
        moveEvents: MoveEventsBuilder.() -> Unit
    ): List<MotionEvent> {
        return buildGesture(
            startEvents = { x, y -> startEvents(x, y) },
            moveEvents = moveEventsBuilder.getEvents(moveEvents),
        )
    }

    private fun startEvents(x: Float, y: Float) = listOf(twoFingerEvent(ACTION_DOWN, x, y))

    private fun twoFingerEvent(
        action: Int,
        x: Float = DEFAULT_X,
        y: Float = DEFAULT_Y
        y: Float = DEFAULT_Y,
    ): MotionEvent {
        return touchpadEvent(
            action = action,
            x = x,
            y = y,
            classification = MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE,
            axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 2f)
            axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 2f),
        )
    }
}

private fun touchpadGesture(
private fun buildGesture(
    startEvents: (Float, Float) -> List<MotionEvent>,
    moveEvents: List<MotionEvent>,
    endEvents: (Float, Float) -> List<MotionEvent>
    endEvents: (Float, Float) -> List<MotionEvent> = { _, _ -> emptyList() },
): List<MotionEvent> {
    val lastX = moveEvents.last().x
    val lastY = moveEvents.last().y
    return startEvents(DEFAULT_X, DEFAULT_Y) + moveEvents + endEvents(lastX, lastY)
}

class GestureBuilder internal constructor(val eventBuilder: (Int, Float, Float) -> MotionEvent) {
class MoveEventsBuilder internal constructor(val eventBuilder: (Int, Float, Float) -> MotionEvent) {

    val events = mutableListOf<MotionEvent>()

@@ -178,3 +228,11 @@ class GestureBuilder internal constructor(val eventBuilder: (Int, Float, Float)
        events.add(eventBuilder(ACTION_MOVE, DEFAULT_X + deltaX, DEFAULT_Y + deltaY))
    }
}

private fun MoveEventsBuilder.getEvents(
    moveEvents: MoveEventsBuilder.() -> Unit
): List<MotionEvent> {
    events.clear()
    this.moveEvents()
    return events
}
Loading