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

Commit 62e6eb97 authored by Michał Brzeziński's avatar Michał Brzeziński Committed by Android (Google) Code Review
Browse files

Merge "Listening to setting changes in StickyKeyRepository" into main

parents 4987500b 2e1b6610
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -43,4 +43,13 @@ class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogB
            { "new sticky keys state received: $str1" }
        )
    }

    fun logNewSettingValue(enabled: Boolean) {
        buffer.log(
            TAG,
            LogLevel.INFO,
            { bool1 = enabled },
            { "sticky key setting changed, new state: ${if (bool1) "enabled" else "disabled"}" }
        )
    }
}
+34 −4
Original line number Diff line number Diff line
@@ -19,8 +19,10 @@ package com.android.systemui.keyboard.stickykeys.data.repository
import android.hardware.input.InputManager
import android.hardware.input.InputManager.StickyModifierStateListener
import android.hardware.input.StickyModifierState
import android.provider.Settings
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
import com.android.systemui.keyboard.stickykeys.shared.model.Locked
@@ -30,14 +32,19 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import javax.inject.Inject

interface StickyKeysRepository {
@@ -45,11 +52,15 @@ interface StickyKeysRepository {
    val settingEnabled: Flow<Boolean>
}

@SysUISingleton
@OptIn(ExperimentalCoroutinesApi::class)
class StickyKeysRepositoryImpl
@Inject
constructor(
    private val inputManager: InputManager,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    private val secureSettings: SecureSettings,
    userRepository: UserRepository,
    private val stickyKeysLogger: StickyKeysLogger,
) : StickyKeysRepository {

@@ -66,8 +77,26 @@ constructor(
            .onEach { stickyKeysLogger.logNewStickyKeysReceived(it) }
            .flowOn(backgroundDispatcher)

    // TODO(b/319837892): Implement reading actual setting
    override val settingEnabled: StateFlow<Boolean> = MutableStateFlow(true)
    override val settingEnabled: Flow<Boolean> =
        userRepository.selectedUserInfo
            .flatMapLatest { stickyKeySettingObserver(it.id) }
            .flowOn(backgroundDispatcher)

    private fun stickyKeySettingObserver(userId: Int): Flow<Boolean> {
        return secureSettings
            .observerFlow(userId, SETTING_KEY)
            .onStart { emit(Unit) }
            .map { isSettingEnabledForCurrentUser(userId) }
            .distinctUntilChanged()
            .onEach { stickyKeysLogger.logNewSettingValue(it) }
    }

    private fun isSettingEnabledForCurrentUser(userId: Int) =
        secureSettings.getIntForUser(
            /* name= */ SETTING_KEY,
            /* default= */ 0,
            /* userHandle= */ userId
        ) != 0

    private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> {
        val keys = linkedMapOf<ModifierKey, Locked>()
@@ -88,5 +117,6 @@ constructor(

    companion object {
        const val TAG = "StickyKeysRepositoryImpl"
        const val SETTING_KEY = Settings.Secure.ACCESSIBILITY_STICKY_KEYS
    }
}
+98 −0
Original line number Diff line number Diff line
package com.android.systemui.keyboard.stickykeys.data.repository

import android.content.pm.UserInfo
import android.hardware.input.InputManager
import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class StickyKeysRepositoryImplTest : SysuiTestCase() {

    private val dispatcher = StandardTestDispatcher()
    private val testScope = TestScope(dispatcher)
    private val secureSettings = FakeSettings()
    private val userRepository = Kosmos().fakeUserRepository
    private lateinit var stickyKeysRepository: StickyKeysRepositoryImpl

    @Before
    fun setup() {
        stickyKeysRepository = StickyKeysRepositoryImpl(
            mock<InputManager>(),
            dispatcher,
            secureSettings,
            userRepository,
            mock<StickyKeysLogger>()
        )
        userRepository.setUserInfos(USER_INFOS)
        setStickyKeySettingForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
        setStickyKeySettingForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
    }

    @Test
    fun settingEnabledEmitsValueForCurrentUser() {
        testScope.runTest {
            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)

            val enabled by collectLastValue(stickyKeysRepository.settingEnabled)

            assertThat(enabled).isTrue()
        }
    }

    @Test
    fun settingEnabledEmitsNewValueWhenSettingChanges() {
        testScope.runTest {
            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
            val enabled by collectValues(stickyKeysRepository.settingEnabled)
            runCurrent()

            setStickyKeySettingForUser(enabled = false, userInfo = SETTING_ENABLED_USER)

            assertThat(enabled).containsExactly(true, false).inOrder()
        }
    }

    @Test
    fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
        testScope.runTest {
            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
            val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
            runCurrent()

            userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)

            assertThat(enabled).isFalse()
        }
    }

    private fun setStickyKeySettingForUser(enabled: Boolean, userInfo: UserInfo) {
        val newValue = if (enabled) "1" else "0"
        secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, userInfo.id)
    }

    private companion object {
        val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
        val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
        val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
    }
}
 No newline at end of file
+52 −7
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.keyboard.stickykeys.ui.viewmodel

import android.hardware.input.InputManager
import android.hardware.input.StickyModifierState
import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -31,8 +32,11 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -57,6 +61,8 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
    private lateinit var viewModel: StickyKeysIndicatorViewModel
    private val inputManager = mock<InputManager>()
    private val keyboardRepository = FakeKeyboardRepository()
    private val secureSettings = FakeSettings()
    private val userRepository = Kosmos().fakeUserRepository
    private val captor =
        ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java)

@@ -65,8 +71,11 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
        val stickyKeysRepository = StickyKeysRepositoryImpl(
            inputManager,
            dispatcher,
            secureSettings,
            userRepository,
            mock<StickyKeysLogger>()
        )
        setStickyKeySetting(enabled = false)
        viewModel =
            StickyKeysIndicatorViewModel(
                stickyKeysRepository = stickyKeysRepository,
@@ -76,13 +85,24 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
    }

    @Test
    fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnected() {
    fun doesntListenToStickyKeysOnlyWhenKeyboardIsConnected() {
        testScope.runTest {
            collectLastValue(viewModel.indicatorContent)

            keyboardRepository.setIsAnyKeyboardConnected(true)
            runCurrent()

            verifyZeroInteractions(inputManager)
        }
    }

    @Test
    fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnectedAndSettingIsOn() {
        testScope.runTest {
            collectLastValue(viewModel.indicatorContent)
            keyboardRepository.setIsAnyKeyboardConnected(true)

            setStickyKeySetting(enabled = true)
            runCurrent()

            verify(inputManager)
@@ -93,11 +113,31 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
        }
    }

    private fun setStickyKeySetting(enabled: Boolean) {
        val newValue = if (enabled) "1" else "0"
        val defaultUser = userRepository.getSelectedUserInfo().id
        secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, defaultUser)
    }

    @Test
    fun stopsListeningToStickyKeysWhenStickyKeySettingsIsTurnedOff() {
        testScope.runTest {
            collectLastValue(viewModel.indicatorContent)
            setStickyKeysActive()
            runCurrent()

            setStickyKeySetting(enabled = false)
            runCurrent()

            verify(inputManager).unregisterStickyModifierStateListener(any())
        }
    }

    @Test
    fun stopsListeningToStickyKeysWhenKeyboardDisconnects() {
        testScope.runTest {
            collectLastValue(viewModel.indicatorContent)
            keyboardRepository.setIsAnyKeyboardConnected(true)
            setStickyKeysActive()
            runCurrent()

            keyboardRepository.setIsAnyKeyboardConnected(false)
@@ -111,7 +151,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
    fun emitsStickyKeysListWhenStickyKeyIsPressed() {
        testScope.runTest {
            val stickyKeys by collectLastValue(viewModel.indicatorContent)
            keyboardRepository.setIsAnyKeyboardConnected(true)
            setStickyKeysActive()

            setStickyKeys(mapOf(ALT to false))

@@ -123,7 +163,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
    fun emitsEmptyListWhenNoStickyKeysAreActive() {
        testScope.runTest {
            val stickyKeys by collectLastValue(viewModel.indicatorContent)
            keyboardRepository.setIsAnyKeyboardConnected(true)
            setStickyKeysActive()

            setStickyKeys(emptyMap())

@@ -135,7 +175,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
    fun passesAllStickyKeysToDialog() {
        testScope.runTest {
            val stickyKeys by collectLastValue(viewModel.indicatorContent)
            keyboardRepository.setIsAnyKeyboardConnected(true)
            setStickyKeysActive()

            setStickyKeys(mapOf(
                ALT to false,
@@ -154,7 +194,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
    fun showsOnlyLockedStateIfKeyIsStickyAndLocked() {
        testScope.runTest {
            val stickyKeys by collectLastValue(viewModel.indicatorContent)
            keyboardRepository.setIsAnyKeyboardConnected(true)
            setStickyKeysActive()

            setStickyKeys(mapOf(
                ALT to false,
@@ -168,7 +208,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
    fun doesNotChangeOrderOfKeysIfTheyBecomeLocked() {
        testScope.runTest {
            val stickyKeys by collectLastValue(viewModel.indicatorContent)
            keyboardRepository.setIsAnyKeyboardConnected(true)
            setStickyKeysActive()

            setStickyKeys(mapOf(
                META to false,
@@ -186,6 +226,11 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
        }
    }

    private fun setStickyKeysActive() {
        keyboardRepository.setIsAnyKeyboardConnected(true)
        setStickyKeySetting(enabled = true)
    }

    private fun TestScope.setStickyKeys(keys: Map<ModifierKey, Boolean>) {
        runCurrent()
        verify(inputManager).registerStickyModifierStateListener(any(), captor.capture())