summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Michal Brzezinski <brzezinski@google.com> 2024-12-19 22:53:02 +0000
committer Michal Brzezinski <brzezinski@google.com> 2024-12-20 18:57:19 +0000
commit936c1902879ee8e4488831dcbd20b80b10e28d71 (patch)
tree688a520cf2bf699f196b5f9e7fa73cde74a2a72b
parentc18bf421a9714e7dc51bd705a1390a46dbe18c9e (diff)
1/n refactoring ViewModels for touchpad tutorial
Extracting handling of easter egg to separate ViewModel to not repeat it in all gesture view models 1. Simplifying TouchpadGestureHandler so that it accepts any gesture recognizers and is not directly dependent on easter egg monitor 2. EasterEggMonitor now extends GestureRecognizer 3. Easter egg handling logic is extracted to separate ViewModel Bug: 384509663 Test: EasterEggGestureViewModelTest Flag: com.android.systemui.shared.new_touchpad_gestures_tutorial Change-Id: I46dd032a992f27252dd95510b77ccaf30f78096d
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGesture.kt76
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt67
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModelTest.kt78
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureMonitor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModel.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt6
19 files changed, 308 insertions, 126 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGesture.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGesture.kt
new file mode 100644
index 000000000000..68b5772bd7ac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGesture.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.graphics.PointF
+import android.view.MotionEvent
+import kotlin.math.cos
+import kotlin.math.sin
+
+/** Test helper to generate circular gestures or full EasterEgg gesture */
+object EasterEggGesture {
+
+ fun motionEventsForGesture(): List<MotionEvent> {
+ val gesturePath = generateCircularGesturePoints(circlesCount = 3)
+ val events =
+ TwoFingerGesture.eventsForFullGesture { gesturePath.forEach { p -> move(p.x, p.y) } }
+ return events
+ }
+
+ /**
+ * Generates list of points that would make up clockwise circular motion with given [radius].
+ * [circlesCount] determines how many full circles gesture should perform. [radiusNoiseFraction]
+ * can introduce noise to mimic real-world gesture which is not perfect - shape will be still
+ * circular but radius at any given point can be deviate from given radius by
+ * [radiusNoiseFraction].
+ */
+ fun generateCircularGesturePoints(
+ circlesCount: Int,
+ radiusNoiseFraction: Double? = null,
+ radius: Float = 100f,
+ ): List<PointF> {
+ val pointsPerCircle = 50
+ val angleStep = 360 / pointsPerCircle
+ val angleBuffer = 20 // buffer to make sure we're doing a bit more than 360 degree
+ val totalAngle = circlesCount * (360 + angleBuffer)
+ // Because all gestures in tests should start at (DEFAULT_X, DEFAULT_Y) we need to shift
+ // circle center x coordinate by radius
+ val centerX = -radius
+ val centerY = 0f
+
+ val randomNoise: (Double) -> Double =
+ if (radiusNoiseFraction == null) {
+ { 0.0 }
+ } else {
+ { radianAngle -> sin(radianAngle * 2) * radiusNoiseFraction }
+ }
+
+ val events = mutableListOf<PointF>()
+ var currentAngle = 0f
+ // as cos(0) == 1 and sin(0) == 0 we start gesture at position of (radius, 0) and go
+ // clockwise - first Y increases and X decreases
+ while (currentAngle < totalAngle) {
+ val radianAngle = Math.toRadians(currentAngle.toDouble())
+ val radiusWithNoise = radius * (1 + randomNoise(radianAngle).toFloat())
+ val x = centerX + radiusWithNoise * cos(radianAngle).toFloat()
+ val y = centerY + radiusWithNoise * sin(radianAngle).toFloat()
+ events.add(PointF(x, y))
+ currentAngle += angleStep
+ }
+ return events
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
index ff0cec5e06e9..e1f590c7f55b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
@@ -16,14 +16,14 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
+import android.graphics.PointF
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.MultiFingerGesture.Companion.SWIPE_DISTANCE
+import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGesture.generateCircularGesturePoints
import com.google.common.truth.Truth.assertThat
-import kotlin.math.cos
-import kotlin.math.sin
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -31,14 +31,14 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class EasterEggGestureTest : SysuiTestCase() {
- private data class Point(val x: Float, val y: Float)
-
private var triggered = false
- private val handler =
- TouchpadGestureHandler(
- BackGestureRecognizer(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt()),
- EasterEggGestureMonitor(callback = { triggered = true }),
- )
+ private val gestureRecognizer = EasterEggGestureMonitor()
+ private val handler = TouchpadGestureHandler(gestureRecognizer)
+
+ @Before
+ fun setup() {
+ gestureRecognizer.addGestureStateCallback { triggered = it == GestureState.Finished }
+ }
@Test
fun easterEggTriggeredAfterThreeCircles() {
@@ -103,52 +103,9 @@ class EasterEggGestureTest : SysuiTestCase() {
assertThat(triggered).isEqualTo(wasTriggered)
}
- private fun assertStateAfterTwoFingerGesture(gesturePath: List<Point>, wasTriggered: Boolean) {
+ private fun assertStateAfterTwoFingerGesture(gesturePath: List<PointF>, wasTriggered: Boolean) {
val events =
- TwoFingerGesture.eventsForFullGesture { gesturePath.forEach { (x, y) -> move(x, y) } }
+ TwoFingerGesture.eventsForFullGesture { gesturePath.forEach { p -> move(p.x, p.y) } }
assertStateAfterEvents(events = events, wasTriggered = wasTriggered)
}
-
- /**
- * Generates list of points that would make up clockwise circular motion with given [radius].
- * [circlesCount] determines how many full circles gesture should perform. [radiusNoiseFraction]
- * can introduce noise to mimic real-world gesture which is not perfect - shape will be still
- * circular but radius at any given point can be deviate from given radius by
- * [radiusNoiseFraction].
- */
- private fun generateCircularGesturePoints(
- circlesCount: Int,
- radiusNoiseFraction: Double? = null,
- radius: Float = 100f,
- ): List<Point> {
- val pointsPerCircle = 50
- val angleStep = 360 / pointsPerCircle
- val angleBuffer = 20 // buffer to make sure we're doing a bit more than 360 degree
- val totalAngle = circlesCount * (360 + angleBuffer)
- // Because all gestures in tests should start at (DEFAULT_X, DEFAULT_Y) we need to shift
- // circle center x coordinate by radius
- val centerX = -radius
- val centerY = 0f
-
- val events = mutableListOf<Point>()
- val randomNoise: (Double) -> Double =
- if (radiusNoiseFraction == null) {
- { 0.0 }
- } else {
- { radianAngle -> sin(radianAngle * 2) * radiusNoiseFraction }
- }
-
- var currentAngle = 0f
- // as cos(0) == 1 and sin(0) == 0 we start gesture at position of (radius, 0) and go
- // clockwise - first Y increases and X decreases
- while (currentAngle < totalAngle) {
- val radianAngle = Math.toRadians(currentAngle.toDouble())
- val radiusWithNoise = radius * (1 + randomNoise(radianAngle).toFloat())
- val x = centerX + radiusWithNoise * cos(radianAngle).toFloat()
- val y = centerY + radiusWithNoise * sin(radianAngle).toFloat()
- events.add(Point(x, y))
- currentAngle += angleStep
- }
- return events
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
index c302b40fc4d7..8eb79ebc3bb1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
@@ -38,7 +38,7 @@ class TouchpadGestureHandlerTest : SysuiTestCase() {
private var gestureState: GestureState = GestureState.NotStarted
private val gestureRecognizer =
BackGestureRecognizer(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt())
- private val handler = TouchpadGestureHandler(gestureRecognizer, EasterEggGestureMonitor {})
+ private val handler = TouchpadGestureHandler(gestureRecognizer)
@Before
fun before() {
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 f90e14caca75..79c1f9fcf517 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
@@ -54,13 +54,6 @@ class BackGestureScreenViewModelTest : SysuiTestCase() {
}
@Test
- fun easterEggNotTriggeredAtStart() =
- kosmos.runTest {
- val easterEggTriggered by collectLastValue(viewModel.easterEggTriggered)
- assertThat(easterEggTriggered).isFalse()
- }
-
- @Test
fun emitsProgressStateWithLeftProgressAnimation() =
kosmos.runTest {
assertProgressWhileMovingFingers(
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
new file mode 100644
index 000000000000..4af374287c62
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModelTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+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.testKosmos
+import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGesture
+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 EasterEggGestureViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val viewModel = EasterEggGestureViewModel()
+
+ @Before
+ fun before() {
+ kosmos.useUnconfinedTestDispatcher()
+ }
+
+ @Test
+ fun easterEggNotTriggeredAtStart() =
+ kosmos.runTest {
+ val easterEggTriggered by collectLastValue(viewModel.easterEggTriggered)
+ assertThat(easterEggTriggered).isFalse()
+ }
+
+ @Test
+ fun emitsTrueOnEasterEggTriggered() =
+ kosmos.runTest {
+ assertStateAfterEvents(
+ events = EasterEggGesture.motionEventsForGesture(),
+ expected = true,
+ )
+ }
+
+ @Test
+ fun emitsFalseOnEasterEggCallbackExecuted() =
+ kosmos.runTest {
+ val easterEggTriggered by collectLastValue(viewModel.easterEggTriggered)
+ EasterEggGesture.motionEventsForGesture().forEach { viewModel.accept(it) }
+
+ assertThat(easterEggTriggered).isEqualTo(true)
+ viewModel.onEasterEggFinished()
+ assertThat(easterEggTriggered).isEqualTo(false)
+ }
+
+ private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: Boolean) {
+ val state by collectLastValue(viewModel.easterEggTriggered)
+ events.forEach { viewModel.accept(it) }
+ assertThat(state).isEqualTo(expected)
+ }
+}
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 3c06352ace97..4dfd01a91f17 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
@@ -70,13 +70,6 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() {
}
@Test
- fun easterEggNotTriggeredAtStart() =
- kosmos.runTest {
- val easterEggTriggered by collectLastValue(viewModel.easterEggTriggered)
- assertThat(easterEggTriggered).isFalse()
- }
-
- @Test
fun emitsProgressStateWithAnimationMarkers() =
kosmos.runTest {
assertStateAfterEvents(
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 a2d8a8b3cb0e..66bf778a754b 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
@@ -74,13 +74,6 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() {
}
@Test
- fun easterEggNotTriggeredAtStart() =
- kosmos.runTest {
- val easterEggTriggered by collectLastValue(viewModel.easterEggTriggered)
- assertThat(easterEggTriggered).isFalse()
- }
-
- @Test
fun emitsProgressStateWithAnimationMarkers() =
kosmos.runTest {
assertStateAfterEvents(
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 fbf7072cc0a0..a6c066500054 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialModule.kt
@@ -31,6 +31,7 @@ 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.BackGestureScreenViewModel
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.HomeGestureScreenViewModel
import dagger.Binds
import dagger.Module
@@ -53,7 +54,11 @@ interface TouchpadTutorialModule {
backGestureScreenViewModel: BackGestureScreenViewModel,
homeGestureScreenViewModel: HomeGestureScreenViewModel,
): TouchpadTutorialScreensProvider {
- return ScreensProvider(backGestureScreenViewModel, homeGestureScreenViewModel)
+ return ScreensProvider(
+ backGestureScreenViewModel,
+ homeGestureScreenViewModel,
+ EasterEggGestureViewModel(),
+ )
}
@SysUISingleton
@@ -74,14 +79,25 @@ interface TouchpadTutorialModule {
private class ScreensProvider(
val backGestureScreenViewModel: BackGestureScreenViewModel,
val homeGestureScreenViewModel: HomeGestureScreenViewModel,
+ val easterEggGestureViewModel: EasterEggGestureViewModel,
) : TouchpadTutorialScreensProvider {
@Composable
override fun BackGesture(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
- BackGestureTutorialScreen(backGestureScreenViewModel, onDoneButtonClicked, onBack)
+ BackGestureTutorialScreen(
+ backGestureScreenViewModel,
+ easterEggGestureViewModel,
+ onDoneButtonClicked,
+ onBack,
+ )
}
@Composable
override fun HomeGesture(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
- HomeGestureTutorialScreen(homeGestureScreenViewModel, onDoneButtonClicked, onBack)
+ HomeGestureTutorialScreen(
+ homeGestureScreenViewModel,
+ easterEggGestureViewModel,
+ onDoneButtonClicked,
+ onBack,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index 804a764b5349..ae32b7a6175c 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -25,10 +25,12 @@ import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenCon
import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.viewmodel.BackGestureScreenViewModel
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel
@Composable
fun BackGestureTutorialScreen(
viewModel: BackGestureScreenViewModel,
+ easterEggGestureViewModel: EasterEggGestureViewModel,
onDoneButtonClicked: () -> Unit,
onBack: () -> Unit,
) {
@@ -49,9 +51,12 @@ fun BackGestureTutorialScreen(
GestureTutorialScreen(
screenConfig = screenConfig,
gestureUiStateFlow = viewModel.gestureUiState,
- motionEventConsumer = viewModel::handleEvent,
- easterEggTriggeredFlow = viewModel.easterEggTriggered,
- onEasterEggFinished = viewModel::onEasterEggFinished,
+ motionEventConsumer = {
+ easterEggGestureViewModel.accept(it)
+ viewModel.handleEvent(it)
+ },
+ easterEggTriggeredFlow = easterEggGestureViewModel.easterEggTriggered,
+ onEasterEggFinished = easterEggGestureViewModel::onEasterEggFinished,
onDoneButtonClicked = onDoneButtonClicked,
onBack = onBack,
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index 5dcd788ea4fd..4f1f40dc4c05 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -23,11 +23,13 @@ import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.HomeGestureScreenViewModel
@Composable
fun HomeGestureTutorialScreen(
viewModel: HomeGestureScreenViewModel,
+ easterEggGestureViewModel: EasterEggGestureViewModel,
onDoneButtonClicked: () -> Unit,
onBack: () -> Unit,
) {
@@ -48,9 +50,12 @@ fun HomeGestureTutorialScreen(
GestureTutorialScreen(
screenConfig = screenConfig,
gestureUiStateFlow = viewModel.gestureUiState,
- motionEventConsumer = viewModel::handleEvent,
- easterEggTriggeredFlow = viewModel.easterEggTriggered,
- onEasterEggFinished = viewModel::onEasterEggFinished,
+ motionEventConsumer = {
+ easterEggGestureViewModel.accept(it)
+ viewModel.handleEvent(it)
+ },
+ easterEggTriggeredFlow = easterEggGestureViewModel.easterEggTriggered,
+ onEasterEggFinished = easterEggGestureViewModel::onEasterEggFinished,
onDoneButtonClicked = onDoneButtonClicked,
onBack = onBack,
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
index 7ff838981950..6c9e26c4b7ea 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
@@ -23,11 +23,13 @@ import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.EasterEggGestureViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureScreenViewModel
@Composable
fun RecentAppsGestureTutorialScreen(
viewModel: RecentAppsGestureScreenViewModel,
+ easterEggGestureViewModel: EasterEggGestureViewModel,
onDoneButtonClicked: () -> Unit,
onBack: () -> Unit,
) {
@@ -49,9 +51,12 @@ fun RecentAppsGestureTutorialScreen(
GestureTutorialScreen(
screenConfig = screenConfig,
gestureUiStateFlow = viewModel.gestureUiState,
- motionEventConsumer = viewModel::handleEvent,
- easterEggTriggeredFlow = viewModel.easterEggTriggered,
- onEasterEggFinished = viewModel::onEasterEggFinished,
+ motionEventConsumer = {
+ easterEggGestureViewModel.accept(it)
+ viewModel.handleEvent(it)
+ },
+ easterEggTriggeredFlow = easterEggGestureViewModel.easterEggTriggered,
+ onEasterEggFinished = easterEggGestureViewModel::onEasterEggFinished,
onDoneButtonClicked = onDoneButtonClicked,
onBack = onBack,
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureMonitor.kt
index 7483840d1933..7befb570b647 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureMonitor.kt
@@ -25,10 +25,12 @@ import kotlin.math.sqrt
/**
* Monitor recognizing easter egg gesture, that is at least [CIRCLES_COUNT_THRESHOLD] circles
- * clockwise within one gesture. It tries to be on the safer side of not triggering gesture if we're
- * not sure if full circle was done.
+ * clockwise within one two-fingers gesture. It tries to be on the safer side of not triggering
+ * gesture if we're not sure if full circle was done.
*/
-class EasterEggGestureMonitor(private val callback: () -> Unit) {
+class EasterEggGestureMonitor : GestureRecognizer {
+
+ private var gestureStateChangedCallback: (GestureState) -> Unit = {}
private var last: Point = Point(0f, 0f)
private var cumulativeAngle: Float = 0f
@@ -39,7 +41,16 @@ class EasterEggGestureMonitor(private val callback: () -> Unit) {
private val points = mutableListOf<Point>()
- fun processTouchpadEvent(event: MotionEvent) {
+ override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
+ gestureStateChangedCallback = callback
+ }
+
+ override fun clearGestureStateCallback() {
+ gestureStateChangedCallback = {}
+ }
+
+ override fun accept(event: MotionEvent) {
+ if (!isTwoFingerSwipe(event)) return
when (event.action) {
MotionEvent.ACTION_DOWN -> {
reset()
@@ -75,7 +86,7 @@ class EasterEggGestureMonitor(private val callback: () -> Unit) {
// without checking if gesture is circular we can have gesture doing arches back and
// forth that finally reaches full circle angle
if (circleCount >= CIRCLES_COUNT_THRESHOLD && wasGestureCircular(points)) {
- callback()
+ gestureStateChangedCallback(GestureState.Finished)
}
reset()
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
index dd275bd11d1e..f417c4c84f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
@@ -24,10 +24,7 @@ import java.util.function.Consumer
* Allows listening to touchpadGesture and calling onDone when gesture was triggered. Can have all
* motion events passed to [onMotionEvent] and will filter touchpad events accordingly
*/
-class TouchpadGestureHandler(
- private val gestureRecognizer: Consumer<MotionEvent>,
- private val easterEggGestureMonitor: EasterEggGestureMonitor,
-) {
+class TouchpadGestureHandler(private vararg val eventConsumers: Consumer<MotionEvent>) {
fun onMotionEvent(event: MotionEvent): Boolean {
// events from touchpad have SOURCE_MOUSE and not SOURCE_TOUCHPAD because of legacy reasons
@@ -38,10 +35,7 @@ class TouchpadGestureHandler(
event.actionMasked == MotionEvent.ACTION_DOWN &&
event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
return if (isFromTouchpad && !buttonClick) {
- if (isTwoFingerSwipe(event)) {
- easterEggGestureMonitor.processTouchpadEvent(event)
- }
- gestureRecognizer.accept(event)
+ eventConsumers.forEach { it.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 6b4cbab3ae09..cefe382a299c 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
@@ -39,6 +39,7 @@ import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialS
import com.android.systemui.touchpad.tutorial.ui.composable.RecentAppsGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.TutorialSelectionScreen
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.HomeGestureScreenViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.RecentAppsGestureScreenViewModel
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE
@@ -73,6 +74,7 @@ constructor(
backGestureViewModel,
homeGestureViewModel,
recentAppsGestureViewModel,
+ EasterEggGestureViewModel(),
closeTutorial = ::finishTutorial,
)
}
@@ -105,6 +107,7 @@ fun TouchpadTutorialScreen(
backGestureViewModel: BackGestureScreenViewModel,
homeGestureViewModel: HomeGestureScreenViewModel,
recentAppsGestureViewModel: RecentAppsGestureScreenViewModel,
+ easterEggGestureViewModel: EasterEggGestureViewModel,
closeTutorial: () -> Unit,
) {
val activeScreen by vm.screen.collectAsStateWithLifecycle(STARTED)
@@ -130,18 +133,21 @@ fun TouchpadTutorialScreen(
BACK_GESTURE ->
BackGestureTutorialScreen(
backGestureViewModel,
+ easterEggGestureViewModel,
onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
onBack = { vm.goTo(TUTORIAL_SELECTION) },
)
HOME_GESTURE ->
HomeGestureTutorialScreen(
homeGestureViewModel,
+ easterEggGestureViewModel,
onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
onBack = { vm.goTo(TUTORIAL_SELECTION) },
)
RECENT_APPS_GESTURE ->
RecentAppsGestureTutorialScreen(
recentAppsGestureViewModel,
+ easterEggGestureViewModel,
onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
onBack = { vm.goTo(TUTORIAL_SELECTION) },
)
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 0154c910be91..b5ed25b8a0da 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
@@ -22,7 +22,6 @@ 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.EasterEggGestureMonitor
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
@@ -32,7 +31,6 @@ import com.android.systemui.util.kotlin.pairwiseBy
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
@@ -40,9 +38,6 @@ class BackGestureScreenViewModel
@Inject
constructor(configurationInteractor: ConfigurationInteractor) : TouchpadTutorialScreenViewModel {
- private val easterEggMonitor = EasterEggGestureMonitor { easterEggTriggered.value = true }
- override val easterEggTriggered = MutableStateFlow(false)
-
private var handler: TouchpadGestureHandler? = null
private val distanceThreshold: Flow<Int> =
@@ -55,7 +50,7 @@ constructor(configurationInteractor: ConfigurationInteractor) : TouchpadTutorial
distanceThreshold
.flatMapLatest {
val recognizer = BackGestureRecognizer(gestureDistanceThresholdPx = it)
- handler = TouchpadGestureHandler(recognizer, easterEggMonitor)
+ handler = TouchpadGestureHandler(recognizer)
GestureFlowAdapter(recognizer).gestureStateAsFlow
}
.pairwiseBy(GestureState.NotStarted) { previous, current ->
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
new file mode 100644
index 000000000000..e539f0aa8abf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/EasterEggGestureViewModel.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.viewmodel
+
+import android.view.MotionEvent
+import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGestureMonitor
+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.TouchpadGestureHandler
+import java.util.function.Consumer
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.receiveAsFlow
+
+class EasterEggGestureViewModel(
+ easterEggMonitor: EasterEggGestureMonitor = EasterEggGestureMonitor()
+) : Consumer<MotionEvent> {
+
+ private val handler = TouchpadGestureHandler(easterEggMonitor)
+
+ private val gestureDone =
+ GestureFlowAdapter(easterEggMonitor).gestureStateAsFlow.filter {
+ it == GestureState.Finished
+ }
+
+ private val easterEggFinished = Channel<Unit>()
+
+ val easterEggTriggered =
+ merge(
+ gestureDone.map { Event.GestureFinished },
+ easterEggFinished.receiveAsFlow().map { Event.StateRestarted },
+ )
+ .map {
+ when (it) {
+ Event.GestureFinished -> true
+ Event.StateRestarted -> false
+ }
+ }
+ .onStart { emit(false) }
+
+ override fun accept(event: MotionEvent) {
+ handler.onMotionEvent(event)
+ }
+
+ fun onEasterEggFinished() {
+ easterEggFinished.trySend(Unit)
+ }
+
+ private sealed interface Event {
+ data object GestureFinished : Event
+
+ data object StateRestarted : Event
+ }
+}
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 1c865f57b8c7..569cc936bd0b 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
@@ -23,7 +23,6 @@ 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.EasterEggGestureMonitor
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
@@ -33,7 +32,6 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.VerticalVelocityTracker
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
@@ -47,9 +45,6 @@ constructor(
val velocityTracker: VelocityTracker = VerticalVelocityTracker(),
) : TouchpadTutorialScreenViewModel {
- private val easterEggMonitor = EasterEggGestureMonitor { easterEggTriggered.value = true }
- override val easterEggTriggered = MutableStateFlow(false)
-
private var handler: TouchpadGestureHandler? = null
private val distanceThreshold: Flow<Int> =
@@ -73,7 +68,7 @@ constructor(
velocityThresholdPxPerMs = velocity,
velocityTracker = velocityTracker,
)
- handler = TouchpadGestureHandler(recognizer, easterEggMonitor)
+ handler = TouchpadGestureHandler(recognizer)
GestureFlowAdapter(recognizer).gestureStateAsFlow
}
.map { toGestureUiState(it) }
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 09947a8b109e..989a60878733 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
@@ -23,7 +23,6 @@ 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.EasterEggGestureMonitor
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
@@ -33,7 +32,6 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.VerticalVelocityTracker
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
@@ -47,9 +45,6 @@ constructor(
private val velocityTracker: VelocityTracker = VerticalVelocityTracker(),
) : TouchpadTutorialScreenViewModel {
- private val easterEggMonitor = EasterEggGestureMonitor { easterEggTriggered.value = true }
- override val easterEggTriggered = MutableStateFlow(false)
-
private var handler: TouchpadGestureHandler? = null
private val distanceThreshold: Flow<Int> =
@@ -77,7 +72,7 @@ constructor(
velocityThresholdPxPerMs = velocity,
velocityTracker = velocityTracker,
)
- handler = TouchpadGestureHandler(recognizer, easterEggMonitor)
+ handler = TouchpadGestureHandler(recognizer)
GestureFlowAdapter(recognizer).gestureStateAsFlow
}
.map { toGestureUiState(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt
index 500f6a0238c3..31e953d6643c 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt
@@ -19,15 +19,9 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel
import android.view.MotionEvent
import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
interface TouchpadTutorialScreenViewModel {
val gestureUiState: Flow<GestureUiState>
- val easterEggTriggered: MutableStateFlow<Boolean>
-
- fun onEasterEggFinished() {
- easterEggTriggered.value = false
- }
fun handleEvent(event: MotionEvent): Boolean
}