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

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

Merge "Adding overview gesture monitor" into main

parents 362532c4 f4220259
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ class BackGestureMonitor(
    override val gestureDistanceThresholdPx: Int,
    override val gestureStateChangedCallback: (GestureState) -> Unit
) :
    TouchpadGestureMonitor by ThreeFingerGestureMonitor(
    TouchpadGestureMonitor by ThreeFingerDistanceBasedGestureMonitor(
        gestureDistanceThresholdPx = gestureDistanceThresholdPx,
        gestureStateChangedCallback = gestureStateChangedCallback,
        donePredicate =
+1 −1
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ class HomeGestureMonitor(
    override val gestureDistanceThresholdPx: Int,
    override val gestureStateChangedCallback: (GestureState) -> Unit
) :
    TouchpadGestureMonitor by ThreeFingerGestureMonitor(
    TouchpadGestureMonitor by ThreeFingerDistanceBasedGestureMonitor(
        gestureDistanceThresholdPx = gestureDistanceThresholdPx,
        gestureStateChangedCallback = gestureStateChangedCallback,
        donePredicate =
+71 −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.systemui.touchpad.tutorial.ui.gesture

import android.view.MotionEvent
import androidx.compose.ui.input.pointer.util.VelocityTracker1D
import kotlin.math.abs

/**
 * Monitors recent apps gesture completion. That is - using three fingers on touchpad - swipe up
 * over some distance threshold and then slow down gesture before fingers are lifted. Implementation
 * is based on [com.android.quickstep.util.TriggerSwipeUpTouchTracker]
 */
class RecentAppsGestureMonitor(
    override val gestureDistanceThresholdPx: Int,
    override val gestureStateChangedCallback: (GestureState) -> Unit,
    private val velocityThresholdPxPerMs: Float,
    private val velocityTracker: VelocityTracker1D = VelocityTracker1D(isDataDifferential = false),
) : TouchpadGestureMonitor {

    private var xStart = 0f
    private var yStart = 0f

    override fun processTouchpadEvent(event: MotionEvent) {
        val action = event.actionMasked
        velocityTracker.addDataPoint(event.eventTime, event.y)
        when (action) {
            MotionEvent.ACTION_DOWN -> {
                if (isThreeFingerTouchpadSwipe(event)) {
                    xStart = event.x
                    yStart = event.y
                    gestureStateChangedCallback(GestureState.IN_PROGRESS)
                }
            }
            MotionEvent.ACTION_UP -> {
                if (isThreeFingerTouchpadSwipe(event) && isRecentAppsGesture(event)) {
                    gestureStateChangedCallback(GestureState.FINISHED)
                } else {
                    gestureStateChangedCallback(GestureState.NOT_STARTED)
                }
                velocityTracker.resetTracking()
            }
            MotionEvent.ACTION_CANCEL -> {
                velocityTracker.resetTracking()
            }
        }
    }

    private fun isRecentAppsGesture(event: MotionEvent): Boolean {
        // below is trying to mirror behavior of TriggerSwipeUpTouchTracker#onGestureEnd.
        // We're diving velocity by 1000, to have the same unit of measure: pixels/ms.
        val swipeDistance = yStart - event.y
        val velocity = velocityTracker.calculateVelocity() / 1000
        return swipeDistance >= gestureDistanceThresholdPx &&
            abs(velocity) <= velocityThresholdPxPerMs
    }
}
+6 −2
Original line number Diff line number Diff line
@@ -25,8 +25,12 @@ interface GestureDonePredicate {
    fun wasGestureDone(startX: Float, startY: Float, endX: Float, endY: Float): Boolean
}

/** Common implementation for all three-finger gesture monitors */
class ThreeFingerGestureMonitor(
/**
 * Common implementation for three-finger gesture monitors that are only distance-based. E.g. recent
 * apps gesture is not only distance-based because it requires going over threshold distance and
 * slowing down the movement.
 */
class ThreeFingerDistanceBasedGestureMonitor(
    override val gestureDistanceThresholdPx: Int,
    override val gestureStateChangedCallback: (GestureState) -> Unit,
    private val donePredicate: GestureDonePredicate
+112 −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.systemui.touchpad.tutorial.ui.gesture

import android.view.MotionEvent
import androidx.compose.ui.input.pointer.util.VelocityTracker1D
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
class RecentAppsGestureMonitorTest : SysuiTestCase() {

    companion object {
        const val THRESHOLD_VELOCITY_PX_PER_MS = 0.1f
        // multiply by 1000 to get px per ms instead of px per s which is unit used by velocity
        // tracker
        const val SLOW = THRESHOLD_VELOCITY_PX_PER_MS * 1000 - 1
        const val FAST = THRESHOLD_VELOCITY_PX_PER_MS * 1000 + 1
    }

    private var gestureState = NOT_STARTED
    private val velocityTracker =
        mock<VelocityTracker1D> {
            // by default return correct speed for the gesture - as if pointer is slowing down
            on { calculateVelocity() } doReturn SLOW
        }
    private val gestureMonitor =
        RecentAppsGestureMonitor(
            gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
            gestureStateChangedCallback = { gestureState = it },
            velocityThresholdPxPerMs = THRESHOLD_VELOCITY_PX_PER_MS,
            velocityTracker = velocityTracker
        )

    @Test
    fun triggersGestureFinishedForThreeFingerGestureUp() {
        assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = FINISHED)
    }

    @Test
    fun doesntTriggerGestureFinished_onGestureSpeedTooHigh() {
        whenever(velocityTracker.calculateVelocity()).thenReturn(FAST)
        assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = NOT_STARTED)
    }

    @Test
    fun triggersGestureProgressForThreeFingerGestureStarted() {
        assertStateAfterEvents(
            events = ThreeFingerGesture.startEvents(x = 0f, y = 0f),
            expectedState = IN_PROGRESS
        )
    }

    @Test
    fun doesntTriggerGestureFinished_onGestureDistanceTooShort() {
        assertStateAfterEvents(
            events = ThreeFingerGesture.swipeUp(distancePx = SWIPE_DISTANCE / 2),
            expectedState = NOT_STARTED
        )
    }

    @Test
    fun doesntTriggerGestureFinished_onThreeFingersSwipeInOtherDirections() {
        assertStateAfterEvents(events = ThreeFingerGesture.swipeDown(), expectedState = NOT_STARTED)
        assertStateAfterEvents(events = ThreeFingerGesture.swipeLeft(), expectedState = NOT_STARTED)
        assertStateAfterEvents(
            events = ThreeFingerGesture.swipeRight(),
            expectedState = NOT_STARTED
        )
    }

    @Test
    fun doesntTriggerGestureFinished_onTwoFingersSwipe() {
        assertStateAfterEvents(events = TwoFingerGesture.swipeUp(), expectedState = NOT_STARTED)
    }

    @Test
    fun doesntTriggerGestureFinished_onFourFingersSwipe() {
        assertStateAfterEvents(events = FourFingerGesture.swipeUp(), expectedState = NOT_STARTED)
    }

    private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
        events.forEach { gestureMonitor.processTouchpadEvent(it) }
        assertThat(gestureState).isEqualTo(expectedState)
    }
}