summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt8
8 files changed, 234 insertions, 4 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
new file mode 100644
index 000000000000..01dbc6bf396c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 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.education.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.education.GestureType
+import com.android.systemui.shared.education.GestureType.BACK_GESTURE
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val repository = kosmos.contextualEducationRepository
+ private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
+
+ @Before
+ fun setup() {
+ underTest.start()
+ }
+
+ @Test
+ fun newEducationInfoOnMaxSignalCountReached() =
+ testScope.runTest {
+ tryTriggeringEducation(BACK_GESTURE)
+ val model by collectLastValue(underTest.educationTriggered)
+ assertThat(model?.gestureType).isEqualTo(BACK_GESTURE)
+ }
+
+ @Test
+ fun noEducationInfoBeforeMaxSignalCountReached() =
+ testScope.runTest {
+ repository.incrementSignalCount(BACK_GESTURE)
+ val model by collectLastValue(underTest.educationTriggered)
+ assertThat(model).isNull()
+ }
+
+ @Test
+ fun noEducationInfoWhenShortcutTriggeredPreviously() =
+ testScope.runTest {
+ val model by collectLastValue(underTest.educationTriggered)
+ repository.updateShortcutTriggerTime(BACK_GESTURE)
+ tryTriggeringEducation(BACK_GESTURE)
+ assertThat(model).isNull()
+ }
+
+ private suspend fun tryTriggeringEducation(gestureType: GestureType) {
+ // Increment max number of signal to try triggering education
+ for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
+ repository.incrementSignalCount(gestureType)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index 0e2e2e6277f2..b8019ab9ce0c 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -22,6 +22,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.education.data.repository.ContextualEducationRepository
import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl
import com.android.systemui.education.domain.interactor.ContextualEducationInteractor
+import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor
import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor
import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractorImpl
import com.android.systemui.shared.education.GestureType
@@ -73,7 +74,7 @@ interface ContextualEducationModule {
implLazy.get()
} else {
// No-op implementation when the flag is disabled.
- return NoOpCoreStartable
+ return NoOpContextualEducationInteractor
}
}
@@ -88,6 +89,18 @@ interface ContextualEducationModule {
return NoOpKeyboardTouchpadEduStatsInteractor
}
}
+
+ @Provides
+ fun provideKeyboardTouchpadEduInteractor(
+ implLazy: Lazy<KeyboardTouchpadEduInteractor>
+ ): CoreStartable {
+ return if (Flags.keyboardTouchpadContextualEducation()) {
+ implLazy.get()
+ } else {
+ // No-op implementation when the flag is disabled.
+ return NoOpKeyboardTouchpadEduInteractor
+ }
+ }
}
private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor {
@@ -96,7 +109,11 @@ interface ContextualEducationModule {
override fun updateShortcutTriggerTime(gestureType: GestureType) {}
}
- private object NoOpCoreStartable : CoreStartable {
+ private object NoOpContextualEducationInteractor : CoreStartable {
+ override fun start() {}
+ }
+
+ private object NoOpKeyboardTouchpadEduInteractor : CoreStartable {
override fun start() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
index e2aa9111246b..3036d970e985 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
@@ -19,12 +19,17 @@ package com.android.systemui.education.domain.interactor
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.education.data.repository.ContextualEducationRepository
import com.android.systemui.shared.education.GestureType
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
/**
@@ -36,16 +41,28 @@ class ContextualEducationInteractor
@Inject
constructor(
@Background private val backgroundScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
private val selectedUserInteractor: SelectedUserInteractor,
private val repository: ContextualEducationRepository,
) : CoreStartable {
+ val backGestureModelFlow = readEduModelsOnSignalCountChanged(GestureType.BACK_GESTURE)
+
override fun start() {
backgroundScope.launch {
selectedUserInteractor.selectedUser.collectLatest { repository.setUser(it) }
}
}
+ private fun readEduModelsOnSignalCountChanged(gestureType: GestureType): Flow<GestureEduModel> {
+ return repository
+ .readGestureEduModelFlow(gestureType)
+ .distinctUntilChanged(
+ areEquivalent = { old, new -> old.signalCount == new.signalCount }
+ )
+ .flowOn(backgroundDispatcher)
+ }
+
suspend fun incrementSignalCount(gestureType: GestureType) =
repository.incrementSignalCount(gestureType)
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
new file mode 100644
index 000000000000..247abf1a7ecc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 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.education.domain.interactor
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.education.shared.model.EducationInfo
+import com.android.systemui.education.shared.model.EducationUiType
+import com.android.systemui.shared.education.GestureType.BACK_GESTURE
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.launch
+
+/** Allow listening to new contextual education triggered */
+@SysUISingleton
+class KeyboardTouchpadEduInteractor
+@Inject
+constructor(
+ @Background private val backgroundScope: CoroutineScope,
+ private val contextualEducationInteractor: ContextualEducationInteractor
+) : CoreStartable {
+
+ companion object {
+ const val MAX_SIGNAL_COUNT: Int = 2
+ }
+
+ private val _educationTriggered = MutableStateFlow<EducationInfo?>(null)
+ val educationTriggered = _educationTriggered.asStateFlow()
+
+ override fun start() {
+ backgroundScope.launch {
+ contextualEducationInteractor.backGestureModelFlow
+ .mapNotNull { getEduType(it) }
+ .collect { _educationTriggered.value = EducationInfo(BACK_GESTURE, it) }
+ }
+ }
+
+ private fun getEduType(model: GestureEduModel): EducationUiType? {
+ if (isEducationNeeded(model)) {
+ return EducationUiType.Toast
+ } else {
+ return null
+ }
+ }
+
+ private fun isEducationNeeded(model: GestureEduModel): Boolean {
+ // Todo: b/354884305 - add complete education logic to show education in correct scenarios
+ val shortcutWasTriggered = model.lastShortcutTriggeredTime == null
+ val signalCountReached = model.signalCount >= MAX_SIGNAL_COUNT
+
+ return shortcutWasTriggered && signalCountReached
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt b/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt
new file mode 100644
index 000000000000..85f4012ddbd2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 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.education.shared.model
+
+import com.android.systemui.shared.education.GestureType
+
+/**
+ * Model for education triggered. [gestureType] indicates what gesture it is trying to educate about
+ * and [educationUiType] is how we educate user in the UI
+ */
+data class EducationInfo(val gestureType: GestureType, val educationUiType: EducationUiType)
+
+enum class EducationUiType {
+ Toast,
+ Notification,
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
index 5410882c9283..bade91a55534 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -28,11 +28,15 @@ class FakeContextualEducationRepository(private val clock: Clock) : ContextualEd
private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
private val _gestureEduModels = MutableStateFlow(GestureEduModel())
private val gestureEduModelsFlow = _gestureEduModels.asStateFlow()
+ private var currentUser: Int = 0
override fun setUser(userId: Int) {
if (!userGestureMap.contains(userId)) {
userGestureMap[userId] = GestureEduModel()
}
+ // save data of current user to the map
+ userGestureMap[currentUser] = _gestureEduModels.value
+ // switch to data of new user
_gestureEduModels.value = userGestureMap[userId]!!
}
@@ -41,13 +45,15 @@ class FakeContextualEducationRepository(private val clock: Clock) : ContextualEd
}
override suspend fun incrementSignalCount(gestureType: GestureType) {
+ val originalModel = _gestureEduModels.value
_gestureEduModels.value =
- GestureEduModel(
+ originalModel.copy(
signalCount = _gestureEduModels.value.signalCount + 1,
)
}
override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
- _gestureEduModels.value = GestureEduModel(lastShortcutTriggeredTime = clock.instant())
+ val originalModel = _gestureEduModels.value
+ _gestureEduModels.value = originalModel.copy(lastShortcutTriggeredTime = clock.instant())
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
index 5b2dc2b39e27..a7b322b5a86d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.education.domain.interactor
import com.android.systemui.education.data.repository.contextualEducationRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.user.domain.interactor.selectedUserInteractor
@@ -25,6 +26,7 @@ val Kosmos.contextualEducationInteractor by
Kosmos.Fixture {
ContextualEducationInteractor(
backgroundScope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
repository = contextualEducationRepository,
selectedUserInteractor = selectedUserInteractor
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
index 8f84e0482b83..fb4e9012f79d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -19,6 +19,14 @@ package com.android.systemui.education.domain.interactor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+var Kosmos.keyboardTouchpadEduInteractor by
+ Kosmos.Fixture {
+ KeyboardTouchpadEduInteractor(
+ backgroundScope = testScope.backgroundScope,
+ contextualEducationInteractor = contextualEducationInteractor
+ )
+ }
+
var Kosmos.keyboardTouchpadEduStatsInteractor by
Kosmos.Fixture {
KeyboardTouchpadEduStatsInteractorImpl(