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 Original line Diff line number Diff line
@@ -43,4 +43,13 @@ class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogB
            { "new sticky keys state received: $str1" }
            { "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 Original line 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
import android.hardware.input.InputManager.StickyModifierStateListener
import android.hardware.input.InputManager.StickyModifierStateListener
import android.hardware.input.StickyModifierState
import android.hardware.input.StickyModifierState
import android.provider.Settings
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
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.dagger.qualifiers.Background
import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
import com.android.systemui.keyboard.stickykeys.shared.model.Locked
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.CTRL
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
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.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import javax.inject.Inject
import javax.inject.Inject


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


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


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


    // TODO(b/319837892): Implement reading actual setting
    override val settingEnabled: Flow<Boolean> =
    override val settingEnabled: StateFlow<Boolean> = MutableStateFlow(true)
        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> {
    private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> {
        val keys = linkedMapOf<ModifierKey, Locked>()
        val keys = linkedMapOf<ModifierKey, Locked>()
@@ -88,5 +117,6 @@ constructor(


    companion object {
    companion object {
        const val TAG = "StickyKeysRepositoryImpl"
        const val TAG = "StickyKeysRepositoryImpl"
        const val SETTING_KEY = Settings.Secure.ACCESSIBILITY_STICKY_KEYS
    }
    }
}
}
+98 −0
Original line number Original line 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 Original line 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.InputManager
import android.hardware.input.StickyModifierState
import android.hardware.input.StickyModifierState
import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
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.CTRL
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
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.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -57,6 +61,8 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
    private lateinit var viewModel: StickyKeysIndicatorViewModel
    private lateinit var viewModel: StickyKeysIndicatorViewModel
    private val inputManager = mock<InputManager>()
    private val inputManager = mock<InputManager>()
    private val keyboardRepository = FakeKeyboardRepository()
    private val keyboardRepository = FakeKeyboardRepository()
    private val secureSettings = FakeSettings()
    private val userRepository = Kosmos().fakeUserRepository
    private val captor =
    private val captor =
        ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java)
        ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java)


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


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

            keyboardRepository.setIsAnyKeyboardConnected(true)
            runCurrent()
            runCurrent()

            verifyZeroInteractions(inputManager)
            verifyZeroInteractions(inputManager)
        }
    }


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

            setStickyKeySetting(enabled = true)
            runCurrent()
            runCurrent()


            verify(inputManager)
            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
    @Test
    fun stopsListeningToStickyKeysWhenKeyboardDisconnects() {
    fun stopsListeningToStickyKeysWhenKeyboardDisconnects() {
        testScope.runTest {
        testScope.runTest {
            collectLastValue(viewModel.indicatorContent)
            collectLastValue(viewModel.indicatorContent)
            keyboardRepository.setIsAnyKeyboardConnected(true)
            setStickyKeysActive()
            runCurrent()
            runCurrent()


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


            setStickyKeys(mapOf(ALT to false))
            setStickyKeys(mapOf(ALT to false))


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


            setStickyKeys(emptyMap())
            setStickyKeys(emptyMap())


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


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


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


            setStickyKeys(mapOf(
            setStickyKeys(mapOf(
                META to false,
                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>) {
    private fun TestScope.setStickyKeys(keys: Map<ModifierKey, Boolean>) {
        runCurrent()
        runCurrent()
        verify(inputManager).registerStickyModifierStateListener(any(), captor.capture())
        verify(inputManager).registerStickyModifierStateListener(any(), captor.capture())