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

Commit 2e1b6610 authored by Michal Brzezinski's avatar Michal Brzezinski Committed by Michał Brzeziński
Browse files

Listening to setting changes in StickyKeyRepository

Implementing settingEnabled: Flow<Boolean> that listens to changes in
Settings.Secure.ACCESSIBILITY_STICKY_KEYS which means sticky key listener will be
registered only when user turns on the setting - compared to now listening always when
keyboard is connected. Also supporting per-user setting.

Test: StickyKeysIndicatorViewModelTest
Test: StickyKeysRepositoryImplTest
Test: switch between users having enabled/disabled sticky key setting and see if behaviour is correct
Flag: ACONFIG com.android.hardware.input.keyboard_a11y_sticky_keys_flag TRUNKFOOD
Fixes: 319837892
Change-Id: Iebe5d6aa38e633a81d38a0fb95645a91ed2575b7
parent cbd3466e
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())