diff options
5 files changed, 248 insertions, 13 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt new file mode 100644 index 000000000000..1f733472cbed --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt @@ -0,0 +1,82 @@ +/* + * 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.ui.view + +import android.content.applicationContext +import android.widget.Toast +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.contextualeducation.GestureType.BACK +import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor +import com.android.systemui.education.domain.interactor.contextualEducationInteractor +import com.android.systemui.education.domain.interactor.keyboardTouchpadEduInteractor +import com.android.systemui.education.ui.view.ContextualEduUiCoordinator +import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ContextualEduUiCoordinatorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val interactor = kosmos.contextualEducationInteractor + private lateinit var underTest: ContextualEduUiCoordinator + @Mock private lateinit var toast: Toast + + @get:Rule val mockitoRule = MockitoJUnit.rule() + + @Before + fun setUp() { + val viewModel = + ContextualEduViewModel( + kosmos.applicationContext.resources, + kosmos.keyboardTouchpadEduInteractor + ) + underTest = + ContextualEduUiCoordinator(kosmos.applicationCoroutineScope, viewModel) { _ -> toast } + underTest.start() + kosmos.keyboardTouchpadEduInteractor.start() + } + + @Test + @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) + fun showToastOnNewEdu() = + testScope.runTest { + triggerEducation(BACK) + runCurrent() + verify(toast).show() + } + + private suspend fun triggerEducation(gestureType: GestureType) { + for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) { + interactor.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 096556fed258..7e2c9f89fa67 100644 --- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt +++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt @@ -26,6 +26,7 @@ import com.android.systemui.education.domain.interactor.ContextualEducationInter 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.education.ui.view.ContextualEduUiCoordinator import dagger.Binds import dagger.Lazy import dagger.Module @@ -74,7 +75,7 @@ interface ContextualEducationModule { implLazy.get() } else { // No-op implementation when the flag is disabled. - return NoOpContextualEducationInteractor + return NoOpCoreStartable } } @@ -91,6 +92,8 @@ interface ContextualEducationModule { } @Provides + @IntoMap + @ClassKey(KeyboardTouchpadEduInteractor::class) fun provideKeyboardTouchpadEduInteractor( implLazy: Lazy<KeyboardTouchpadEduInteractor> ): CoreStartable { @@ -98,22 +101,32 @@ interface ContextualEducationModule { implLazy.get() } else { // No-op implementation when the flag is disabled. - return NoOpKeyboardTouchpadEduInteractor + return NoOpCoreStartable } } - } - - private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor { - override fun incrementSignalCount(gestureType: GestureType) {} - override fun updateShortcutTriggerTime(gestureType: GestureType) {} + @Provides + @IntoMap + @ClassKey(ContextualEduUiCoordinator::class) + fun provideContextualEduUiCoordinator( + implLazy: Lazy<ContextualEduUiCoordinator> + ): CoreStartable { + return if (Flags.keyboardTouchpadContextualEducation()) { + implLazy.get() + } else { + // No-op implementation when the flag is disabled. + return NoOpCoreStartable + } + } } +} - private object NoOpContextualEducationInteractor : CoreStartable { - override fun start() {} - } +private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor { + override fun incrementSignalCount(gestureType: GestureType) {} - private object NoOpKeyboardTouchpadEduInteractor : CoreStartable { - override fun start() {} - } + override fun updateShortcutTriggerTime(gestureType: GestureType) {} +} + +private object NoOpCoreStartable : CoreStartable { + override fun start() {} } diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt new file mode 100644 index 000000000000..b446ea2bcb38 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt @@ -0,0 +1,68 @@ +/* + * 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.ui.view + +import android.content.Context +import android.widget.Toast +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.education.shared.model.EducationUiType +import com.android.systemui.education.ui.viewmodel.ContextualEduContentViewModel +import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * A class to show contextual education on UI based on the edu produced from + * [ContextualEduViewModel] + */ +@SysUISingleton +class ContextualEduUiCoordinator +constructor( + @Application private val applicationScope: CoroutineScope, + private val viewModel: ContextualEduViewModel, + private val createToast: (String) -> Toast +) : CoreStartable { + + @Inject + constructor( + @Application applicationScope: CoroutineScope, + context: Context, + viewModel: ContextualEduViewModel, + ) : this( + applicationScope, + viewModel, + createToast = { message -> Toast.makeText(context, message, Toast.LENGTH_LONG) } + ) + + override fun start() { + applicationScope.launch { + viewModel.eduContent.collect { contentModel -> + if (contentModel.type == EducationUiType.Toast) { + showToast(contentModel) + } + } + } + } + + private fun showToast(model: ContextualEduContentViewModel) { + val toast = createToast(model.message) + toast.show() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt new file mode 100644 index 000000000000..3cba4c8fb110 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt @@ -0,0 +1,21 @@ +/* + * 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.ui.viewmodel + +import com.android.systemui.education.shared.model.EducationUiType + +data class ContextualEduContentViewModel(val message: String, val type: EducationUiType) diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt new file mode 100644 index 000000000000..58276e0759f6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt @@ -0,0 +1,51 @@ +/* + * 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.ui.viewmodel + +import android.content.res.Resources +import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor +import com.android.systemui.education.shared.model.EducationInfo +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map + +@SysUISingleton +class ContextualEduViewModel +@Inject +constructor(@Main private val resources: Resources, interactor: KeyboardTouchpadEduInteractor) { + val eduContent: Flow<ContextualEduContentViewModel> = + interactor.educationTriggered.filterNotNull().map { + ContextualEduContentViewModel(getEduContent(it), it.educationUiType) + } + + private fun getEduContent(educationInfo: EducationInfo): String { + // Todo: also check UiType in educationInfo to determine the string + val resourceId = + when (educationInfo.gestureType) { + GestureType.BACK -> R.string.back_edu_toast_content + GestureType.HOME -> R.string.home_edu_toast_content + GestureType.OVERVIEW -> R.string.overview_edu_toast_content + GestureType.ALL_APPS -> R.string.all_apps_edu_toast_content + } + return resources.getString(resourceId) + } +} |