Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c93ecf43 authored by Helen Cheuk's avatar Helen Cheuk Committed by Android (Google) Code Review
Browse files

Merge "[Contextual Edu] Add UiCoordinator and view models for displaying education" into main

parents 4cdca176 7e3f949f
Loading
Loading
Loading
Loading
+82 −0
Original line number Diff line number Diff line
/*
 * 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)
        }
    }
}
+26 −13
Original line number Diff line number Diff line
@@ -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,7 +101,22 @@ interface ContextualEducationModule {
                implLazy.get()
            } else {
                // No-op implementation when the flag is disabled.
                return NoOpKeyboardTouchpadEduInteractor
                return NoOpCoreStartable
            }
        }

        @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
            }
        }
    }
}
@@ -109,11 +127,6 @@ interface ContextualEducationModule {
    override fun updateShortcutTriggerTime(gestureType: GestureType) {}
}

    private object NoOpContextualEducationInteractor : CoreStartable {
private object NoOpCoreStartable : CoreStartable {
    override fun start() {}
}

    private object NoOpKeyboardTouchpadEduInteractor : CoreStartable {
        override fun start() {}
    }
}
+68 −0
Original line number Diff line number Diff line
/*
 * 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()
    }
}
+21 −0
Original line number Diff line number Diff line
/*
 * 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)
+51 −0
Original line number Diff line number Diff line
/*
 * 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)
    }
}