diff options
19 files changed, 432 insertions, 174 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt index 79c1f9fcf517..4aec88e8497b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt @@ -16,12 +16,13 @@ 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.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest @@ -34,18 +35,27 @@ import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finis import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.InProgress import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture +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 BackGestureScreenViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() + private val resources = kosmos.mockResources private val fakeConfigRepository = kosmos.fakeConfigurationRepository - private val viewModel = BackGestureScreenViewModel(kosmos.configurationInteractor) + private val viewModel = + BackGestureScreenViewModel( + GestureRecognizerAdapter( + BackGestureRecognizerProvider(kosmos.touchpadGestureResources), + kosmos.inputDeviceTutorialLogger, + ) + ) @Before fun before() { @@ -115,10 +125,12 @@ class BackGestureScreenViewModelTest : SysuiTestCase() { } private fun setThresholdResource(threshold: Float) { - fakeConfigRepository.setDimensionPixelSize( - R.dimen.touchpad_tutorial_gestures_distance_threshold, - (threshold).toInt(), - ) + whenever( + resources.getDimensionPixelSize( + R.dimen.touchpad_tutorial_gestures_distance_threshold + ) + ) + .thenReturn(threshold.toInt()) fakeConfigRepository.onAnyConfigurationChange() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModelTest.kt index 4af374287c62..8bd796b5c851 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModelTest.kt @@ -20,6 +20,7 @@ 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.inputdevice.tutorial.inputDeviceTutorialLogger import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest @@ -36,7 +37,13 @@ import org.junit.runner.RunWith class EasterEggGestureViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() - private val viewModel = EasterEggGestureViewModel() + private val viewModel = + EasterEggGestureViewModel( + GestureRecognizerAdapter( + EasterEggRecognizerProvider(), + kosmos.inputDeviceTutorialLogger, + ) + ) @Before fun before() { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt index 4dfd01a91f17..65a995dcd043 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt @@ -22,7 +22,7 @@ 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.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest @@ -37,6 +37,7 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Comp import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture import com.android.systemui.touchpad.tutorial.ui.gesture.Velocity import com.android.systemui.touchpad.ui.gesture.fakeVelocityTracker +import com.android.systemui.touchpad.ui.gesture.touchpadGestureResources import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -59,7 +60,12 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() { private val resources = kosmos.mockResources private val viewModel = - HomeGestureScreenViewModel(kosmos.configurationInteractor, resources, fakeVelocityTracker) + HomeGestureScreenViewModel( + GestureRecognizerAdapter( + HomeGestureRecognizerProvider(kosmos.touchpadGestureResources, fakeVelocityTracker), + kosmos.inputDeviceTutorialLogger, + ) + ) @Before fun before() { @@ -126,10 +132,12 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() { } private fun setDistanceThreshold(threshold: Float) { - fakeConfigRepository.setDimensionPixelSize( - R.dimen.touchpad_tutorial_gestures_distance_threshold, - (threshold).toInt(), - ) + whenever( + resources.getDimensionPixelSize( + R.dimen.touchpad_tutorial_gestures_distance_threshold + ) + ) + .thenReturn(threshold.toInt()) fakeConfigRepository.onAnyConfigurationChange() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt index 66bf778a754b..1bc60b67095e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt @@ -22,7 +22,7 @@ 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.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest @@ -37,6 +37,7 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Comp import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture import com.android.systemui.touchpad.tutorial.ui.gesture.Velocity import com.android.systemui.touchpad.ui.gesture.fakeVelocityTracker +import com.android.systemui.touchpad.ui.gesture.touchpadGestureResources import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -60,9 +61,13 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() { private val viewModel = RecentAppsGestureScreenViewModel( - kosmos.configurationInteractor, - resources, - fakeVelocityTracker, + GestureRecognizerAdapter( + RecentAppsGestureRecognizerProvider( + kosmos.touchpadGestureResources, + fakeVelocityTracker, + ), + kosmos.inputDeviceTutorialLogger, + ) ) @Before diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt index a6c066500054..c43f31beb5bc 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt @@ -30,9 +30,15 @@ import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialS import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker import com.android.systemui.touchpad.tutorial.ui.gesture.VerticalVelocityTracker import com.android.systemui.touchpad.tutorial.ui.view.TouchpadTutorialActivity +import com.android.systemui.touchpad.tutorial.ui.viewmodel.BackGestureRecognizerProvider import com.android.systemui.touchpad.tutorial.ui.viewmodel.BackGestureScreenViewModel import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel +import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggRecognizerProvider +import com.android.systemui.touchpad.tutorial.ui.viewmodel.GestureRecognizerAdapter +import com.android.systemui.touchpad.tutorial.ui.viewmodel.HomeGestureRecognizerProvider import com.android.systemui.touchpad.tutorial.ui.viewmodel.HomeGestureScreenViewModel +import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureRecognizerProvider +import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureScreenViewModel import dagger.Binds import dagger.Module import dagger.Provides @@ -53,14 +59,46 @@ interface TouchpadTutorialModule { fun touchpadScreensProvider( backGestureScreenViewModel: BackGestureScreenViewModel, homeGestureScreenViewModel: HomeGestureScreenViewModel, + easterEggGestureViewModel: EasterEggGestureViewModel, ): TouchpadTutorialScreensProvider { return ScreensProvider( backGestureScreenViewModel, homeGestureScreenViewModel, - EasterEggGestureViewModel(), + easterEggGestureViewModel, ) } + @Provides + fun recentAppsViewModel( + recognizerProvider: RecentAppsGestureRecognizerProvider, + adapterFactory: GestureRecognizerAdapter.Factory, + ): RecentAppsGestureScreenViewModel { + return RecentAppsGestureScreenViewModel(adapterFactory.create(recognizerProvider)) + } + + @Provides + fun backViewModel( + recognizerProvider: BackGestureRecognizerProvider, + adapterFactory: GestureRecognizerAdapter.Factory, + ): BackGestureScreenViewModel { + return BackGestureScreenViewModel(adapterFactory.create(recognizerProvider)) + } + + @Provides + fun homeViewModel( + recognizerProvider: HomeGestureRecognizerProvider, + adapterFactory: GestureRecognizerAdapter.Factory, + ): HomeGestureScreenViewModel { + return HomeGestureScreenViewModel(adapterFactory.create(recognizerProvider)) + } + + @Provides + fun easterEggViewModel( + adapterFactory: GestureRecognizerAdapter.Factory + ): EasterEggGestureViewModel { + return EasterEggGestureViewModel(adapterFactory.create(EasterEggRecognizerProvider())) + } + @SysUISingleton @Provides fun touchpadGesturesInteractor( diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadEventsFilter.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadEventsFilter.kt index bddeb0b25ec2..b4b8ff0a3949 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadEventsFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadEventsFilter.kt @@ -20,6 +20,7 @@ import android.view.InputDevice import android.view.MotionEvent import android.view.MotionEvent.ACTION_DOWN import android.view.MotionEvent.BUTTON_PRIMARY +import com.android.systemui.touchpad.tutorial.ui.viewmodel.GestureRecognizerAdapter object TouchpadEventsFilter { @@ -42,3 +43,12 @@ fun GestureRecognizer.handleTouchpadMotionEvent(event: MotionEvent): Boolean { false } } + +fun GestureRecognizerAdapter.handleTouchpadMotionEvent(event: MotionEvent): Boolean { + return if (TouchpadEventsFilter.isTouchpadAndNonClickEvent(event)) { + this.accept(event) + true + } else { + false + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt index cefe382a299c..3264300ed908 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt @@ -58,6 +58,7 @@ constructor( private val backGestureViewModel: BackGestureScreenViewModel, private val homeGestureViewModel: HomeGestureScreenViewModel, private val recentAppsGestureViewModel: RecentAppsGestureScreenViewModel, + private val easterEggGestureViewModel: EasterEggGestureViewModel, ) : ComponentActivity() { private val tutorialViewModel by @@ -74,7 +75,7 @@ constructor( backGestureViewModel, homeGestureViewModel, recentAppsGestureViewModel, - EasterEggGestureViewModel(), + easterEggGestureViewModel, closeTutorial = ::finishTutorial, ) } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureRecognizerProvider.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureRecognizerProvider.kt new file mode 100644 index 000000000000..b089882e9cf8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureRecognizerProvider.kt @@ -0,0 +1,32 @@ +/* + * 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.viewmodel + +import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureRecognizer +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class BackGestureRecognizerProvider @Inject constructor(val resources: TouchpadGestureResources) : + GestureRecognizerProvider { + + override val recognizer: Flow<GestureRecognizer> = + resources.distanceThreshold().map { distance -> + BackGestureRecognizer(gestureDistanceThresholdPx = distance) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt index 93e8d313edcf..8e53669a7841 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt @@ -17,47 +17,26 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel import android.view.MotionEvent -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState -import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureRecognizer import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent import com.android.systemui.util.kotlin.pairwiseBy -import javax.inject.Inject -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest -class BackGestureScreenViewModel -@Inject -constructor(configurationInteractor: ConfigurationInteractor) : TouchpadTutorialScreenViewModel { +class BackGestureScreenViewModel(val gestureRecognizer: GestureRecognizerAdapter) : + TouchpadTutorialScreenViewModel { - private var recognizer: BackGestureRecognizer? = null - - private val distanceThreshold: Flow<Int> = - configurationInteractor - .dimensionPixelSize(R.dimen.touchpad_tutorial_gestures_distance_threshold) - .distinctUntilChanged() - - @OptIn(ExperimentalCoroutinesApi::class) override val gestureUiState: Flow<GestureUiState> = - distanceThreshold - .flatMapLatest { - recognizer = BackGestureRecognizer(gestureDistanceThresholdPx = it) - GestureFlowAdapter(recognizer!!).gestureStateAsFlow - } - .pairwiseBy(GestureState.NotStarted) { previous, current -> - toGestureUiState(current, previous) - } + gestureRecognizer.gestureState.pairwiseBy(GestureState.NotStarted) { previous, current -> + toGestureUiState(current, previous) + } override fun handleEvent(event: MotionEvent): Boolean { - return recognizer?.handleTouchpadMotionEvent(event) ?: false + return gestureRecognizer.handleTouchpadMotionEvent(event) } private fun toGestureUiState(current: GestureState, previous: GestureState): GestureUiState { diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModel.kt index 69cdab6108ab..9ca456dff58a 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModel.kt @@ -17,8 +17,6 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel import android.view.MotionEvent -import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGestureRecognizer -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent import java.util.function.Consumer @@ -29,14 +27,10 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.receiveAsFlow -class EasterEggGestureViewModel( - private val gestureRecognizer: EasterEggGestureRecognizer = EasterEggGestureRecognizer() -) : Consumer<MotionEvent> { +class EasterEggGestureViewModel(val gestureRecognizer: GestureRecognizerAdapter) : + Consumer<MotionEvent> { - private val gestureDone = - GestureFlowAdapter(gestureRecognizer).gestureStateAsFlow.filter { - it == GestureState.Finished - } + private val gestureDone = gestureRecognizer.gestureState.filter { it == GestureState.Finished } private val easterEggFinished = Channel<Unit>() diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureFlowAdapter.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggRecognizerProvider.kt index 23e31b0a9efd..c48ccb52ba0f 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureFlowAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggRecognizerProvider.kt @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.systemui.touchpad.tutorial.ui.gesture +package com.android.systemui.touchpad.tutorial.ui.viewmodel -import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow -import kotlinx.coroutines.channels.awaitClose +import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGestureRecognizer +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow -class GestureFlowAdapter(gestureRecognizer: GestureRecognizer) { +class EasterEggRecognizerProvider : GestureRecognizerProvider { - val gestureStateAsFlow: Flow<GestureState> = conflatedCallbackFlow { - val callback: (GestureState) -> Unit = { trySend(it) } - gestureRecognizer.addGestureStateCallback(callback) - awaitClose { gestureRecognizer.clearGestureStateCallback() } - } + override val recognizer: Flow<GestureRecognizer> = + MutableStateFlow(EasterEggGestureRecognizer()) } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/GestureRecognizerAdapter.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/GestureRecognizerAdapter.kt new file mode 100644 index 000000000000..8e7375fa923b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/GestureRecognizerAdapter.kt @@ -0,0 +1,72 @@ +/* + * 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.viewmodel + +import android.view.MotionEvent +import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.function.Consumer +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest + +/** + * Adapter for [GestureRecognizer] exposing [GestureState] as Flow and ensuring that motion events + * are always handled by latest [GestureRecognizer]. + */ +class GestureRecognizerAdapter +@AssistedInject +constructor( + @Assisted provider: GestureRecognizerProvider, + private val logger: InputDeviceTutorialLogger, +) : Consumer<MotionEvent> { + + private var gestureRecognizer: GestureRecognizer? = null + + @OptIn(ExperimentalCoroutinesApi::class) + val gestureState: Flow<GestureState> = + provider.recognizer.flatMapLatest { + gestureRecognizer = it + gestureStateAsFlow(it) + } + + override fun accept(event: MotionEvent) { + if (gestureRecognizer == null) { + logger.w("sending MotionEvent before gesture recognizer is initialized") + } else { + gestureRecognizer?.accept(event) + } + } + + private fun gestureStateAsFlow(recognizer: GestureRecognizer): Flow<GestureState> = + conflatedCallbackFlow { + val callback: (GestureState) -> Unit = { trySend(it) } + recognizer.addGestureStateCallback(callback) + awaitClose { recognizer.clearGestureStateCallback() } + } + + @AssistedFactory + interface Factory { + fun create(provider: GestureRecognizerProvider): GestureRecognizerAdapter + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/GestureRecognizerProvider.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/GestureRecognizerProvider.kt new file mode 100644 index 000000000000..585bc0c425d0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/GestureRecognizerProvider.kt @@ -0,0 +1,25 @@ +/* + * 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.viewmodel + +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer +import kotlinx.coroutines.flow.Flow + +/** Observes state of the system and provides always up-to-date [GestureRecognizer] */ +interface GestureRecognizerProvider { + val recognizer: Flow<GestureRecognizer> +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureRecognizerProvider.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureRecognizerProvider.kt new file mode 100644 index 000000000000..6d818a6a0398 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureRecognizerProvider.kt @@ -0,0 +1,47 @@ +/* + * 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.viewmodel + +import com.android.systemui.res.R +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer +import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureRecognizer +import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map + +class HomeGestureRecognizerProvider +@Inject +constructor(val resources: TouchpadGestureResources, val velocityTracker: VelocityTracker) : + GestureRecognizerProvider { + + override val recognizer: Flow<GestureRecognizer> = + resources + .distanceThreshold() + .combine( + resources.velocityThreshold(R.dimen.touchpad_home_gesture_velocity_threshold), + { distance, velocity -> distance to velocity }, + ) + .map { (distance, velocity) -> + HomeGestureRecognizer( + gestureDistanceThresholdPx = distance, + velocityThresholdPxPerMs = velocity, + velocityTracker = velocityTracker, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt index 9a817d810bba..9d6f568fa1b1 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt @@ -16,70 +16,27 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel -import android.content.res.Resources import android.view.MotionEvent -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState -import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureRecognizer -import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker -import com.android.systemui.touchpad.tutorial.ui.gesture.VerticalVelocityTracker import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent -import javax.inject.Inject -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map -class HomeGestureScreenViewModel -@Inject -constructor( - val configurationInteractor: ConfigurationInteractor, - @Main val resources: Resources, - val velocityTracker: VelocityTracker = VerticalVelocityTracker(), -) : TouchpadTutorialScreenViewModel { +class HomeGestureScreenViewModel(private val gestureRecognizer: GestureRecognizerAdapter) : + TouchpadTutorialScreenViewModel { - private var recognizer: HomeGestureRecognizer? = null - - private val distanceThreshold: Flow<Int> = - configurationInteractor - .dimensionPixelSize(R.dimen.touchpad_tutorial_gestures_distance_threshold) - .distinctUntilChanged() - - private val velocityThreshold: Flow<Float> = - configurationInteractor.onAnyConfigurationChange - .map { resources.getDimension(R.dimen.touchpad_home_gesture_velocity_threshold) } - .distinctUntilChanged() - - @OptIn(ExperimentalCoroutinesApi::class) override val gestureUiState: Flow<GestureUiState> = - distanceThreshold - .combine(velocityThreshold, { distance, velocity -> distance to velocity }) - .flatMapLatest { (distance, velocity) -> - recognizer = - HomeGestureRecognizer( - gestureDistanceThresholdPx = distance, - velocityThresholdPxPerMs = velocity, - velocityTracker = velocityTracker, - ) - GestureFlowAdapter(recognizer!!).gestureStateAsFlow - } - .map { toGestureUiState(it) } - - private fun toGestureUiState(it: GestureState) = - it.toGestureUiState( - progressStartMarker = "drag with gesture", - progressEndMarker = "release playback realtime", - successAnimation = R.raw.trackpad_home_success, - ) + gestureRecognizer.gestureState.map { + it.toGestureUiState( + progressStartMarker = "drag with gesture", + progressEndMarker = "release playback realtime", + successAnimation = R.raw.trackpad_home_success, + ) + } override fun handleEvent(event: MotionEvent): Boolean { - return recognizer?.handleTouchpadMotionEvent(event) ?: false + return gestureRecognizer.handleTouchpadMotionEvent(event) } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureRecognizerProvider.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureRecognizerProvider.kt new file mode 100644 index 000000000000..3e0b434036e5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureRecognizerProvider.kt @@ -0,0 +1,49 @@ +/* + * 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.viewmodel + +import com.android.systemui.res.R +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer +import com.android.systemui.touchpad.tutorial.ui.gesture.RecentAppsGestureRecognizer +import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map + +class RecentAppsGestureRecognizerProvider +@Inject +constructor(val resources: TouchpadGestureResources, val velocityTracker: VelocityTracker) : + GestureRecognizerProvider { + + override val recognizer: Flow<GestureRecognizer> = + resources + .distanceThreshold() + .combine( + resources.velocityThreshold( + R.dimen.touchpad_recent_apps_gesture_velocity_threshold + ), + { distance, velocity -> distance to velocity }, + ) + .map { (distance, velocity) -> + RecentAppsGestureRecognizer( + gestureDistanceThresholdPx = distance, + velocityThresholdPxPerMs = velocity, + velocityTracker = velocityTracker, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt index 8215078c346d..97528583277f 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt @@ -16,74 +16,27 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel -import android.content.res.Resources import android.view.MotionEvent -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState -import com.android.systemui.touchpad.tutorial.ui.gesture.RecentAppsGestureRecognizer -import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker -import com.android.systemui.touchpad.tutorial.ui.gesture.VerticalVelocityTracker import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent -import javax.inject.Inject -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map -class RecentAppsGestureScreenViewModel -@Inject -constructor( - configurationInteractor: ConfigurationInteractor, - @Main private val resources: Resources, - private val velocityTracker: VelocityTracker = VerticalVelocityTracker(), -) : TouchpadTutorialScreenViewModel { +class RecentAppsGestureScreenViewModel(private val gestureRecognizer: GestureRecognizerAdapter) : + TouchpadTutorialScreenViewModel { - private var recognizer: RecentAppsGestureRecognizer? = null - - private val distanceThreshold: Flow<Int> = - configurationInteractor.onAnyConfigurationChange - .map { - resources.getDimensionPixelSize( - R.dimen.touchpad_tutorial_gestures_distance_threshold - ) - } - .distinctUntilChanged() - - private val velocityThreshold: Flow<Float> = - configurationInteractor.onAnyConfigurationChange - .map { resources.getDimension(R.dimen.touchpad_recent_apps_gesture_velocity_threshold) } - .distinctUntilChanged() - - @OptIn(ExperimentalCoroutinesApi::class) override val gestureUiState: Flow<GestureUiState> = - distanceThreshold - .combine(velocityThreshold, { distance, velocity -> distance to velocity }) - .flatMapLatest { (distance, velocity) -> - recognizer = - RecentAppsGestureRecognizer( - gestureDistanceThresholdPx = distance, - velocityThresholdPxPerMs = velocity, - velocityTracker = velocityTracker, - ) - GestureFlowAdapter(recognizer!!).gestureStateAsFlow - } - .map { toGestureUiState(it) } - - private fun toGestureUiState(it: GestureState) = - it.toGestureUiState( - progressStartMarker = "drag with gesture", - progressEndMarker = "onPause", - successAnimation = R.raw.trackpad_recent_apps_success, - ) + gestureRecognizer.gestureState.map { + it.toGestureUiState( + progressStartMarker = "drag with gesture", + progressEndMarker = "onPause", + successAnimation = R.raw.trackpad_recent_apps_success, + ) + } override fun handleEvent(event: MotionEvent): Boolean { - return recognizer?.handleTouchpadMotionEvent(event) ?: false + return gestureRecognizer.handleTouchpadMotionEvent(event) } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadGestureResources.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadGestureResources.kt new file mode 100644 index 000000000000..3d99bd82d79e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadGestureResources.kt @@ -0,0 +1,46 @@ +/* + * 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.viewmodel + +import android.content.res.Resources +import androidx.annotation.DimenRes +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map + +class TouchpadGestureResources +@Inject +constructor(val configurationInteractor: ConfigurationInteractor, @Main val resources: Resources) { + + fun distanceThreshold(): Flow<Int> = + configurationInteractor.onAnyConfigurationChange + .map { + resources.getDimensionPixelSize( + R.dimen.touchpad_tutorial_gestures_distance_threshold + ) + } + .distinctUntilChanged() + + fun velocityThreshold(@DimenRes resId: Int): Flow<Float> = + configurationInteractor.onAnyConfigurationChange + .map { resources.getDimension(resId) } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/TouchpadGestureResourcesKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/TouchpadGestureResourcesKosmos.kt new file mode 100644 index 000000000000..d795941f96aa --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/TouchpadGestureResourcesKosmos.kt @@ -0,0 +1,25 @@ +/* + * 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.ui.gesture + +import android.content.res.mockResources +import com.android.systemui.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.touchpad.tutorial.ui.viewmodel.TouchpadGestureResources + +var Kosmos.touchpadGestureResources: TouchpadGestureResources by + Kosmos.Fixture { TouchpadGestureResources(configurationInteractor, mockResources) } |