Loading packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizerTest.kt 0 → 100644 +85 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class SwitchAppsGestureRecognizerTest : SysuiTestCase() { private var gestureState: GestureState = NotStarted private val gestureRecognizer = SwitchAppsGestureRecognizer(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt()) @Before fun before() { gestureRecognizer.addGestureStateCallback { gestureState = it } } @Test fun triggersProgressRelativeToDistanceWhenSwipingRight() { assertProgressWhileMovingFingers( deltaX = SWIPE_DISTANCE / 2, expected = InProgress(progress = 0.5f), ) assertProgressWhileMovingFingers( deltaX = SWIPE_DISTANCE, expected = InProgress(progress = 1f), ) } @Test fun triggersProgressDoesNotGoBelowZero() { // going in the wrong direction assertProgressWhileMovingFingers( deltaX = -SWIPE_DISTANCE, expected = InProgress(progress = 0f), ) } @Test fun triggersProgressDoesNotExceedOne() { // going further than required distance assertProgressWhileMovingFingers( deltaX = SWIPE_DISTANCE * 2, expected = InProgress(progress = 1f), ) } private fun assertProgressWhileMovingFingers(deltaX: Float, expected: InProgress) { assertStateAfterEvents( events = FourFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) }, expectedState = expected, ) } private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) { events.forEach { gestureRecognizer.accept(it) } assertThat(gestureState).isEqualTo(expectedState) } } packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModelTest.kt 0 → 100644 +132 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.viewmodel import android.content.res.mockResources import android.view.MotionEvent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.res.R import com.android.systemui.testKosmos import com.android.systemui.touchpad.tutorial.ui.gesture.FourFingerGesture import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.android.systemui.touchpad.ui.gesture.touchpadGestureResources import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class SwitchAppsGestureScreenViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val resources = kosmos.mockResources private val fakeConfigRepository = kosmos.fakeConfigurationRepository private val viewModel = SwitchAppsGestureScreenViewModel( GestureRecognizerAdapter( SwitchAppsGestureRecognizerProvider(kosmos.touchpadGestureResources), kosmos.inputDeviceTutorialLogger, ) ) @Before fun before() { setDistanceThreshold(threshold = SWIPE_DISTANCE - 1) kosmos.useUnconfinedTestDispatcher() } @Test fun emitsProgressStateWithProgressAnimation() = kosmos.runTest { assertProgressWhileMovingFingers( deltaX = SWIPE_DISTANCE, expected = InProgress( progress = 1f, startMarker = "gesture to R", endMarker = "end of gesture", ), ) } @Test fun emitsFinishedStateWithSuccessAnimation() = kosmos.runTest { assertStateAfterEvents( events = FourFingerGesture.swipeRight(), expected = Finished(successAnimation = R.raw.trackpad_switch_apps_success), ) } @Test fun emitErrorStateWhenLatestDistanceThresholdNotReached() = kosmos.runTest { fun performGesture() = FourFingerGesture.swipeRight().forEach { viewModel.handleEvent(it) } val state by collectLastValue(viewModel.tutorialState) performGesture() assertThat(state).isInstanceOf(Finished::class.java) setDistanceThreshold(SWIPE_DISTANCE + 1) performGesture() // now swipe distance is not enough to trigger success assertThat(state).isInstanceOf(Error::class.java) } private fun setDistanceThreshold(threshold: Float) { whenever( resources.getDimensionPixelSize( R.dimen.touchpad_tutorial_gestures_distance_threshold ) ) .thenReturn(threshold.toInt()) fakeConfigRepository.onAnyConfigurationChange() } private fun Kosmos.assertProgressWhileMovingFingers( deltaX: Float, expected: TutorialActionState, ) { assertStateAfterEvents( events = FourFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) }, expected = expected, ) } private fun Kosmos.assertStateAfterEvents( events: List<MotionEvent>, expected: TutorialActionState, ) { val state by collectLastValue(viewModel.tutorialState) events.forEach { viewModel.handleEvent(it) } assertThat(state).isEqualTo(expected) } } packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt +2 −11 Original line number Diff line number Diff line Loading @@ -18,12 +18,9 @@ package com.android.systemui.touchpad.tutorial.ui.gesture import android.util.MathUtils import android.view.MotionEvent import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection.LEFT import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection.RIGHT import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress import kotlin.math.abs /** Recognizes Quickswitch gesture i.e. using four fingers on touchpad, swiping right. */ /** Recognizes Quickswitch gesture i.e. using four fingers on touchpad, swiping to right. */ class SwitchAppsGestureRecognizer(private val gestureDistanceThresholdPx: Int) : GestureRecognizer { private val distanceTracker = DistanceTracker() Loading @@ -50,13 +47,7 @@ class SwitchAppsGestureRecognizer(private val gestureDistanceThresholdPx: Int) : gestureStateChangedCallback, gestureState, isFinished = { it.deltaX >= gestureDistanceThresholdPx }, progress = ::getProgress, progress = { InProgress(MathUtils.saturate(it.deltaX / gestureDistanceThresholdPx)) }, ) } private fun getProgress(it: Moving): InProgress { val direction = if (it.deltaX > 0) RIGHT else LEFT val value = MathUtils.saturate(abs(it.deltaX / gestureDistanceThresholdPx)) return InProgress(value, direction) } } packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt +1 −3 Original line number Diff line number Diff line Loading @@ -18,15 +18,13 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer import com.android.systemui.touchpad.tutorial.ui.gesture.SwitchAppsGestureRecognizer import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map class SwitchAppsGestureRecognizerProvider @Inject constructor(val resources: TouchpadGestureResources, val velocityTracker: VelocityTracker) : GestureRecognizerProvider { constructor(val resources: TouchpadGestureResources) : GestureRecognizerProvider { override val recognizer: Flow<GestureRecognizer> = resources.distanceThreshold().map { Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizerTest.kt 0 → 100644 +85 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class SwitchAppsGestureRecognizerTest : SysuiTestCase() { private var gestureState: GestureState = NotStarted private val gestureRecognizer = SwitchAppsGestureRecognizer(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt()) @Before fun before() { gestureRecognizer.addGestureStateCallback { gestureState = it } } @Test fun triggersProgressRelativeToDistanceWhenSwipingRight() { assertProgressWhileMovingFingers( deltaX = SWIPE_DISTANCE / 2, expected = InProgress(progress = 0.5f), ) assertProgressWhileMovingFingers( deltaX = SWIPE_DISTANCE, expected = InProgress(progress = 1f), ) } @Test fun triggersProgressDoesNotGoBelowZero() { // going in the wrong direction assertProgressWhileMovingFingers( deltaX = -SWIPE_DISTANCE, expected = InProgress(progress = 0f), ) } @Test fun triggersProgressDoesNotExceedOne() { // going further than required distance assertProgressWhileMovingFingers( deltaX = SWIPE_DISTANCE * 2, expected = InProgress(progress = 1f), ) } private fun assertProgressWhileMovingFingers(deltaX: Float, expected: InProgress) { assertStateAfterEvents( events = FourFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) }, expectedState = expected, ) } private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) { events.forEach { gestureRecognizer.accept(it) } assertThat(gestureState).isEqualTo(expectedState) } }
packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModelTest.kt 0 → 100644 +132 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.viewmodel import android.content.res.mockResources import android.view.MotionEvent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.res.R import com.android.systemui.testKosmos import com.android.systemui.touchpad.tutorial.ui.gesture.FourFingerGesture import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.android.systemui.touchpad.ui.gesture.touchpadGestureResources import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class SwitchAppsGestureScreenViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val resources = kosmos.mockResources private val fakeConfigRepository = kosmos.fakeConfigurationRepository private val viewModel = SwitchAppsGestureScreenViewModel( GestureRecognizerAdapter( SwitchAppsGestureRecognizerProvider(kosmos.touchpadGestureResources), kosmos.inputDeviceTutorialLogger, ) ) @Before fun before() { setDistanceThreshold(threshold = SWIPE_DISTANCE - 1) kosmos.useUnconfinedTestDispatcher() } @Test fun emitsProgressStateWithProgressAnimation() = kosmos.runTest { assertProgressWhileMovingFingers( deltaX = SWIPE_DISTANCE, expected = InProgress( progress = 1f, startMarker = "gesture to R", endMarker = "end of gesture", ), ) } @Test fun emitsFinishedStateWithSuccessAnimation() = kosmos.runTest { assertStateAfterEvents( events = FourFingerGesture.swipeRight(), expected = Finished(successAnimation = R.raw.trackpad_switch_apps_success), ) } @Test fun emitErrorStateWhenLatestDistanceThresholdNotReached() = kosmos.runTest { fun performGesture() = FourFingerGesture.swipeRight().forEach { viewModel.handleEvent(it) } val state by collectLastValue(viewModel.tutorialState) performGesture() assertThat(state).isInstanceOf(Finished::class.java) setDistanceThreshold(SWIPE_DISTANCE + 1) performGesture() // now swipe distance is not enough to trigger success assertThat(state).isInstanceOf(Error::class.java) } private fun setDistanceThreshold(threshold: Float) { whenever( resources.getDimensionPixelSize( R.dimen.touchpad_tutorial_gestures_distance_threshold ) ) .thenReturn(threshold.toInt()) fakeConfigRepository.onAnyConfigurationChange() } private fun Kosmos.assertProgressWhileMovingFingers( deltaX: Float, expected: TutorialActionState, ) { assertStateAfterEvents( events = FourFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) }, expected = expected, ) } private fun Kosmos.assertStateAfterEvents( events: List<MotionEvent>, expected: TutorialActionState, ) { val state by collectLastValue(viewModel.tutorialState) events.forEach { viewModel.handleEvent(it) } assertThat(state).isEqualTo(expected) } }
packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt +2 −11 Original line number Diff line number Diff line Loading @@ -18,12 +18,9 @@ package com.android.systemui.touchpad.tutorial.ui.gesture import android.util.MathUtils import android.view.MotionEvent import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection.LEFT import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection.RIGHT import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress import kotlin.math.abs /** Recognizes Quickswitch gesture i.e. using four fingers on touchpad, swiping right. */ /** Recognizes Quickswitch gesture i.e. using four fingers on touchpad, swiping to right. */ class SwitchAppsGestureRecognizer(private val gestureDistanceThresholdPx: Int) : GestureRecognizer { private val distanceTracker = DistanceTracker() Loading @@ -50,13 +47,7 @@ class SwitchAppsGestureRecognizer(private val gestureDistanceThresholdPx: Int) : gestureStateChangedCallback, gestureState, isFinished = { it.deltaX >= gestureDistanceThresholdPx }, progress = ::getProgress, progress = { InProgress(MathUtils.saturate(it.deltaX / gestureDistanceThresholdPx)) }, ) } private fun getProgress(it: Moving): InProgress { val direction = if (it.deltaX > 0) RIGHT else LEFT val value = MathUtils.saturate(abs(it.deltaX / gestureDistanceThresholdPx)) return InProgress(value, direction) } }
packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt +1 −3 Original line number Diff line number Diff line Loading @@ -18,15 +18,13 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer import com.android.systemui.touchpad.tutorial.ui.gesture.SwitchAppsGestureRecognizer import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map class SwitchAppsGestureRecognizerProvider @Inject constructor(val resources: TouchpadGestureResources, val velocityTracker: VelocityTracker) : GestureRecognizerProvider { constructor(val resources: TouchpadGestureResources) : GestureRecognizerProvider { override val recognizer: Flow<GestureRecognizer> = resources.distanceThreshold().map { Loading