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

Commit 61bcfb4c authored by Michal Brzezinski's avatar Michal Brzezinski
Browse files

Adding connectedKeyboards flow to KeyboardRepository

newlyConnectedKeyboard might not be enough if consumer wants to have list of currently connected keyboards.

Fixes: 363860424
Test: KeyboardRepositoryTest
Flag: NONE small change
Change-Id: I8180828f14a1d1d34691deead3e0bf60b8b1e3c8
parent 3bd16e84
Loading
Loading
Loading
Loading
+66 −6
Original line number Original line Diff line number Diff line
@@ -145,7 +145,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {


            fakeInputManager.addPhysicalKeyboard(
            fakeInputManager.addPhysicalKeyboard(
                PHYSICAL_NOT_FULL_KEYBOARD_ID,
                PHYSICAL_NOT_FULL_KEYBOARD_ID,
                isFullKeyboard = false
                isFullKeyboard = false,
            )
            )
            assertThat(isKeyboardConnected).isFalse()
            assertThat(isKeyboardConnected).isFalse()


@@ -223,7 +223,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
            backlightListenerCaptor.value.onBacklightChanged(
            backlightListenerCaptor.value.onBacklightChanged(
                current = 1,
                current = 1,
                max = 5,
                max = 5,
                triggeredByKeyPress = false
                triggeredByKeyPress = false,
            )
            )
            assertThat(backlight).isNull()
            assertThat(backlight).isNull()
        }
        }
@@ -239,7 +239,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
            backlightListenerCaptor.value.onBacklightChanged(
            backlightListenerCaptor.value.onBacklightChanged(
                current = 1,
                current = 1,
                max = 5,
                max = 5,
                triggeredByKeyPress = true
                triggeredByKeyPress = true,
            )
            )
            assertThat(backlight).isNotNull()
            assertThat(backlight).isNotNull()
        }
        }
@@ -318,15 +318,75 @@ class KeyboardRepositoryTest : SysuiTestCase() {
        }
        }
    }
    }


    @Test
    fun connectedKeyboards_emitsAllKeyboards() {
        testScope.runTest {
            val firstKeyboard = Keyboard(vendorId = 1, productId = 1)
            val secondKeyboard = Keyboard(vendorId = 2, productId = 2)
            captureDeviceListener()
            val keyboards by collectLastValueImmediately(underTest.connectedKeyboards)

            fakeInputManager.addPhysicalKeyboard(
                PHYSICAL_FULL_KEYBOARD_ID,
                vendorId = firstKeyboard.vendorId,
                productId = firstKeyboard.productId,
            )
            assertThat(keyboards)
                .containsExactly(Keyboard(firstKeyboard.vendorId, firstKeyboard.productId))

            fakeInputManager.addPhysicalKeyboard(
                ANOTHER_PHYSICAL_FULL_KEYBOARD_ID,
                vendorId = secondKeyboard.vendorId,
                productId = secondKeyboard.productId,
            )
            assertThat(keyboards)
                .containsExactly(
                    Keyboard(firstKeyboard.vendorId, firstKeyboard.productId),
                    Keyboard(secondKeyboard.vendorId, secondKeyboard.productId),
                )
        }
    }

    @Test
    fun connectedKeyboards_emitsOnlyFullPhysicalKeyboards() {
        testScope.runTest {
            captureDeviceListener()
            val keyboards by collectLastValueImmediately(underTest.connectedKeyboards)

            fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
            fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD)
            fakeInputManager.addPhysicalKeyboard(
                PHYSICAL_NOT_FULL_KEYBOARD_ID,
                isFullKeyboard = false,
            )

            assertThat(keyboards).hasSize(1)
        }
    }

    @Test
    fun connectedKeyboards_emitsOnlyConnectedKeyboards() {
        testScope.runTest {
            captureDeviceListener()
            val keyboards by collectLastValueImmediately(underTest.connectedKeyboards)

            fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
            fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
            fakeInputManager.removeDevice(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)

            assertThat(keyboards).hasSize(1)
        }
    }

    private fun KeyboardBacklightListener.onBacklightChanged(
    private fun KeyboardBacklightListener.onBacklightChanged(
        current: Int,
        current: Int,
        max: Int,
        max: Int,
        triggeredByKeyPress: Boolean = true
        triggeredByKeyPress: Boolean = true,
    ) {
    ) {
        onKeyboardBacklightChanged(
        onKeyboardBacklightChanged(
            /* deviceId= */ 0,
            /* deviceId= */ 0,
            TestBacklightState(current, max),
            TestBacklightState(current, max),
            triggeredByKeyPress
            triggeredByKeyPress,
        )
        )
    }
    }


@@ -343,7 +403,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {


    private class TestBacklightState(
    private class TestBacklightState(
        private val brightnessLevel: Int,
        private val brightnessLevel: Int,
        private val maxBrightnessLevel: Int
        private val maxBrightnessLevel: Int,
    ) : KeyboardBacklightState() {
    ) : KeyboardBacklightState() {
        override fun getBrightnessLevel() = brightnessLevel
        override fun getBrightnessLevel() = brightnessLevel


+7 −11
Original line number Original line Diff line number Diff line
@@ -38,7 +38,7 @@ class InputDeviceRepository
constructor(
constructor(
    @Background private val backgroundHandler: Handler,
    @Background private val backgroundHandler: Handler,
    @Background private val backgroundScope: CoroutineScope,
    @Background private val backgroundScope: CoroutineScope,
    private val inputManager: InputManager
    private val inputManager: InputManager,
) {
) {


    sealed interface DeviceChange
    sealed interface DeviceChange
@@ -50,11 +50,11 @@ constructor(
    data object FreshStart : DeviceChange
    data object FreshStart : DeviceChange


    /**
    /**
     * Emits collection of all currently connected keyboards and what was the last [DeviceChange].
     * Emits collection of all currently connected input devices and what was the last
     * It emits collection so that every new subscriber to this SharedFlow can get latest state of
     * [DeviceChange]. It emits collection so that every new subscriber to this SharedFlow can get
     * all keyboards. Otherwise we might get into situation where subscriber timing on
     * latest state of all input devices. Otherwise we might get into situation where subscriber
     * initialization matter and later subscriber will only get latest device and will miss all
     * timing on initialization matter and later subscriber will only get latest device and will
     * previous devices.
     * miss all previous devices.
     */
     */
    // TODO(b/351984587): Replace with StateFlow
    // TODO(b/351984587): Replace with StateFlow
    @SuppressLint("SharedFlowCreation")
    @SuppressLint("SharedFlowCreation")
@@ -79,11 +79,7 @@ constructor(
                inputManager.registerInputDeviceListener(listener, backgroundHandler)
                inputManager.registerInputDeviceListener(listener, backgroundHandler)
                awaitClose { inputManager.unregisterInputDeviceListener(listener) }
                awaitClose { inputManager.unregisterInputDeviceListener(listener) }
            }
            }
            .shareIn(
            .shareIn(scope = backgroundScope, started = SharingStarted.Lazily, replay = 1)
                scope = backgroundScope,
                started = SharingStarted.Lazily,
                replay = 1,
            )


    private fun <T> SendChannel<T>.sendWithLogging(element: T) {
    private fun <T> SendChannel<T>.sendWithLogging(element: T) {
        trySendWithFailureLogging(element, TAG)
        trySendWithFailureLogging(element, TAG)
+2 −0
Original line number Original line Diff line number Diff line
@@ -50,6 +50,8 @@ class CommandLineKeyboardRepository @Inject constructor(commandRegistry: Command
    private val _newlyConnectedKeyboard: MutableStateFlow<Keyboard?> = MutableStateFlow(null)
    private val _newlyConnectedKeyboard: MutableStateFlow<Keyboard?> = MutableStateFlow(null)
    override val newlyConnectedKeyboard: Flow<Keyboard> = _newlyConnectedKeyboard.filterNotNull()
    override val newlyConnectedKeyboard: Flow<Keyboard> = _newlyConnectedKeyboard.filterNotNull()


    override val connectedKeyboards: Flow<Set<Keyboard>> = MutableStateFlow(emptySet())

    init {
    init {
        Log.i(TAG, "initializing shell command $COMMAND")
        Log.i(TAG, "initializing shell command $COMMAND")
        commandRegistry.registerCommand(COMMAND) { KeyboardCommand() }
        commandRegistry.registerCommand(COMMAND) { KeyboardCommand() }
+11 −1
Original line number Original line Diff line number Diff line
@@ -61,6 +61,9 @@ interface KeyboardRepository {
     */
     */
    val newlyConnectedKeyboard: Flow<Keyboard>
    val newlyConnectedKeyboard: Flow<Keyboard>


    /** Emits set of currently connected keyboards */
    val connectedKeyboards: Flow<Set<Keyboard>>

    /**
    /**
     * Emits [BacklightModel] whenever user changes backlight level from keyboard press. Can only
     * Emits [BacklightModel] whenever user changes backlight level from keyboard press. Can only
     * happen when physical keyboard is connected
     * happen when physical keyboard is connected
@@ -74,7 +77,7 @@ class KeyboardRepositoryImpl
constructor(
constructor(
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    private val inputManager: InputManager,
    private val inputManager: InputManager,
    inputDeviceRepository: InputDeviceRepository
    inputDeviceRepository: InputDeviceRepository,
) : KeyboardRepository {
) : KeyboardRepository {


    @FlowPreview
    @FlowPreview
@@ -93,6 +96,13 @@ constructor(
            .mapNotNull { deviceIdToKeyboard(it) }
            .mapNotNull { deviceIdToKeyboard(it) }
            .flowOn(backgroundDispatcher)
            .flowOn(backgroundDispatcher)


    override val connectedKeyboards: Flow<Set<Keyboard>> =
        inputDeviceRepository.deviceChange
            .map { (deviceIds, _) -> deviceIds }
            .map { deviceIds -> deviceIds.filter { isPhysicalFullKeyboard(it) } }
            .distinctUntilChanged()
            .map { deviceIds -> deviceIds.mapNotNull { deviceIdToKeyboard(it) }.toSet() }

    override val isAnyKeyboardConnected: Flow<Boolean> =
    override val isAnyKeyboardConnected: Flow<Boolean> =
        inputDeviceRepository.deviceChange
        inputDeviceRepository.deviceChange
            .map { (ids, _) -> ids.any { id -> isPhysicalFullKeyboard(id) } }
            .map { (ids, _) -> ids.any { id -> isPhysicalFullKeyboard(id) } }
+18 −3
Original line number Original line Diff line number Diff line
@@ -19,8 +19,10 @@ package com.android.systemui.keyboard.data.repository


import com.android.systemui.keyboard.data.model.Keyboard
import com.android.systemui.keyboard.data.model.Keyboard
import com.android.systemui.keyboard.shared.model.BacklightModel
import com.android.systemui.keyboard.shared.model.BacklightModel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.filterNotNull


class FakeKeyboardRepository : KeyboardRepository {
class FakeKeyboardRepository : KeyboardRepository {
@@ -32,8 +34,14 @@ class FakeKeyboardRepository : KeyboardRepository {
    // filtering to make sure backlight doesn't have default initial value
    // filtering to make sure backlight doesn't have default initial value
    override val backlight: Flow<BacklightModel> = _backlightState.filterNotNull()
    override val backlight: Flow<BacklightModel> = _backlightState.filterNotNull()


    private val _newlyConnectedKeyboard: MutableStateFlow<Keyboard?> = MutableStateFlow(null)
    // implemented as channel because original implementation is modeling events: it doesn't hold
    override val newlyConnectedKeyboard: Flow<Keyboard> = _newlyConnectedKeyboard.filterNotNull()
    // state so it won't always emit once connected. And it's bad if some tests depend on that
    // incorrect behaviour.
    private val _newlyConnectedKeyboard: Channel<Keyboard> = Channel()
    override val newlyConnectedKeyboard: Flow<Keyboard> = _newlyConnectedKeyboard.consumeAsFlow()

    private val _connectedKeyboards: MutableStateFlow<Set<Keyboard>> = MutableStateFlow(setOf())
    override val connectedKeyboards: Flow<Set<Keyboard>> = _connectedKeyboards


    fun setBacklight(state: BacklightModel) {
    fun setBacklight(state: BacklightModel) {
        _backlightState.value = state
        _backlightState.value = state
@@ -43,7 +51,14 @@ class FakeKeyboardRepository : KeyboardRepository {
        _isAnyKeyboardConnected.value = connected
        _isAnyKeyboardConnected.value = connected
    }
    }


    fun setConnectedKeyboards(keyboards: Set<Keyboard>) {
        _connectedKeyboards.value = keyboards
        _isAnyKeyboardConnected.value = keyboards.isNotEmpty()
    }

    fun setNewlyConnectedKeyboard(keyboard: Keyboard) {
    fun setNewlyConnectedKeyboard(keyboard: Keyboard) {
        _newlyConnectedKeyboard.value = keyboard
        _newlyConnectedKeyboard.trySend(keyboard)
        _connectedKeyboards.value += keyboard
        _isAnyKeyboardConnected.value = true
    }
    }
}
}