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

Commit d4cdc0c1 authored by Michał Brzeziński's avatar Michał Brzeziński Committed by Android (Google) Code Review
Browse files

Merge "Calculating gesture progress for all touchpad gestures" into main

parents e961c206 ea1f8243
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