diff options
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt | 2 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt | 2 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt | 71 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerDistanceBasedGestureMonitor.kt (renamed from packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureMonitor.kt) | 8 | ||||
| -rw-r--r-- | packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt | 112 |
5 files changed, 191 insertions, 4 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt index e3666ce66d5c..084da2c59776 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt @@ -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 = diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt index a410f991182e..a9aa5c897573 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt @@ -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 = diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt new file mode 100644 index 000000000000..58282393d4a6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt @@ -0,0 +1,71 @@ +/* + * 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 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerDistanceBasedGestureMonitor.kt index 377977ce0d74..9bf0fe96624f 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureMonitor.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerDistanceBasedGestureMonitor.kt @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt new file mode 100644 index 000000000000..cafebb9fd79e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt @@ -0,0 +1,112 @@ +/* + * 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) + } +} |