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

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

Merge "[Contextual Edu] Add PKT education stats interactor" into main

parents 64995e62 a964ce23
Loading
Loading
Loading
Loading
+63 −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.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.education.data.repository.fakeEduClock
import com.android.systemui.kosmos.testScope
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.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyboardTouchpadStatsInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val underTest = kosmos.keyboardTouchpadEduStatsInteractor

    @Test
    fun dataUpdatedOnIncrementSignalCount() =
        testScope.runTest {
            val model by
                collectLastValue(
                    kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK_GESTURE)
                )
            val originalValue = model!!.signalCount
            underTest.incrementSignalCount(BACK_GESTURE)
            assertThat(model?.signalCount).isEqualTo(originalValue + 1)
        }

    @Test
    fun dataAddedOnUpdateShortcutTriggerTime() =
        testScope.runTest {
            val model by
                collectLastValue(
                    kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK_GESTURE)
                )
            assertThat(model?.lastShortcutTriggeredTime).isNull()
            underTest.updateShortcutTriggerTime(BACK_GESTURE)
            assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant())
        }
}
+45 −0
Original line number Diff line number Diff line
@@ -16,12 +16,21 @@

package com.android.systemui.education.dagger

import com.android.systemui.CoreStartable
import com.android.systemui.Flags
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.KeyboardTouchpadEduStatsInteractor
import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractorImpl
import com.android.systemui.shared.education.GestureType
import dagger.Binds
import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import java.time.Clock
import javax.inject.Qualifier
import kotlinx.coroutines.CoroutineDispatcher
@@ -53,5 +62,41 @@ interface ContextualEducationModule {
        fun provideEduClock(): Clock {
            return Clock.systemUTC()
        }

        @Provides
        @IntoMap
        @ClassKey(ContextualEducationInteractor::class)
        fun provideContextualEducationInteractor(
            implLazy: Lazy<ContextualEducationInteractor>
        ): CoreStartable {
            return if (Flags.keyboardTouchpadContextualEducation()) {
                implLazy.get()
            } else {
                // No-op implementation when the flag is disabled.
                return NoOpCoreStartable
            }
        }

        @Provides
        fun provideKeyboardTouchpadEduStatsInteractor(
            implLazy: Lazy<KeyboardTouchpadEduStatsInteractorImpl>
        ): KeyboardTouchpadEduStatsInteractor {
            return if (Flags.keyboardTouchpadContextualEducation()) {
                implLazy.get()
            } else {
                // No-op implementation when the flag is disabled.
                return NoOpKeyboardTouchpadEduStatsInteractor
            }
        }
    }

    private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor {
        override fun incrementSignalCount(gestureType: GestureType) {}

        override fun updateShortcutTriggerTime(gestureType: GestureType) {}
    }

    private object NoOpCoreStartable : CoreStartable {
        override fun start() {}
    }
}
+54 −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.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.repository.ContextualEducationRepository
import com.android.systemui.shared.education.GestureType
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

/**
 * Allows updating education data (e.g. signal count, shortcut time) for different gesture types.
 * Change user education repository when user is changed.
 */
@SysUISingleton
class ContextualEducationInteractor
@Inject
constructor(
    @Background private val backgroundScope: CoroutineScope,
    private val selectedUserInteractor: SelectedUserInteractor,
    private val repository: ContextualEducationRepository,
) : CoreStartable {

    override fun start() {
        backgroundScope.launch {
            selectedUserInteractor.selectedUser.collectLatest { repository.setUser(it) }
        }
    }

    suspend fun incrementSignalCount(gestureType: GestureType) =
        repository.incrementSignalCount(gestureType)

    suspend fun updateShortcutTriggerTime(gestureType: GestureType) =
        repository.updateShortcutTriggerTime(gestureType)
}
+55 −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.interactor

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.shared.education.GestureType
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/**
 * Encapsulates the update functions of KeyboardTouchpadEduStatsInteractor. This encapsulation is
 * for having a different implementation of interactor when the feature flag is off.
 */
interface KeyboardTouchpadEduStatsInteractor {
    fun incrementSignalCount(gestureType: GestureType)

    fun updateShortcutTriggerTime(gestureType: GestureType)
}

/** Allow update to education data related to keyboard/touchpad. */
@SysUISingleton
class KeyboardTouchpadEduStatsInteractorImpl
@Inject
constructor(
    @Background private val backgroundScope: CoroutineScope,
    private val contextualEducationInteractor: ContextualEducationInteractor
) : KeyboardTouchpadEduStatsInteractor {

    override fun incrementSignalCount(gestureType: GestureType) {
        // Todo: check if keyboard/touchpad is connected before update
        backgroundScope.launch { contextualEducationInteractor.incrementSignalCount(gestureType) }
    }

    override fun updateShortcutTriggerTime(gestureType: GestureType) {
        backgroundScope.launch {
            contextualEducationInteractor.updateShortcutTriggerTime(gestureType)
        }
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -17,7 +17,10 @@
package com.android.systemui.education.data.repository

import com.android.systemui.kosmos.Kosmos
import java.time.Clock
import java.time.Instant

var Kosmos.contextualEducationRepository: ContextualEducationRepository by
    Kosmos.Fixture { FakeContextualEducationRepository(FakeEduClock(Instant.MIN)) }
    Kosmos.Fixture { FakeContextualEducationRepository(fakeEduClock) }

var Kosmos.fakeEduClock: Clock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
Loading