diff options
7 files changed, 204 insertions, 36 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractor.kt index 65e70b319923..df2a33f95872 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractor.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyboard.data.repository.KeyboardRepository import com.android.systemui.keyboard.shared.model.BacklightModel import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf @@ -34,8 +35,9 @@ constructor( ) { /** Emits current backlight level as [BacklightModel] or null if keyboard is not connected */ + @ExperimentalCoroutinesApi val backlight: Flow<BacklightModel?> = - keyboardRepository.keyboardConnected.flatMapLatest { connected -> + keyboardRepository.isAnyKeyboardConnected.flatMapLatest { connected -> if (connected) keyboardRepository.backlight else flowOf(null) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/model/Keyboard.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/model/Keyboard.kt new file mode 100644 index 000000000000..ee6dd0de96a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/model/Keyboard.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2023 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.keyboard.data.model + +data class Keyboard(val vendorId: Int, val productId: Int) diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt index 1f1329111ce7..2fac40a48d3d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt @@ -25,22 +25,41 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyboard.data.model.Keyboard import com.android.systemui.keyboard.shared.model.BacklightModel import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.shareIn interface KeyboardRepository { - val keyboardConnected: Flow<Boolean> + /** Emits true if any physical keyboard is connected to the device, false otherwise. */ + val isAnyKeyboardConnected: Flow<Boolean> + + /** + * Emits [Keyboard] object whenever new physical keyboard connects. When SysUI (re)starts it + * emits all currently connected keyboards + */ + val newlyConnectedKeyboard: Flow<Keyboard> + + /** + * Emits [BacklightModel] whenever user changes backlight level from keyboard press. Can only + * happen when physical keyboard is connected + */ val backlight: Flow<BacklightModel> } @@ -53,33 +72,65 @@ constructor( private val inputManager: InputManager, ) : KeyboardRepository { - private val connectedDeviceIds: Flow<Set<Int>> = + private sealed interface DeviceChange + private data class DeviceAdded(val deviceId: Int) : DeviceChange + private object DeviceRemoved : DeviceChange + private object FreshStart : DeviceChange + + /** + * Emits collection of all currently connected keyboards and what was the last [DeviceChange]. + * It emits collection so that every new subscriber to this SharedFlow can get latest state of + * all keyboards. Otherwise we might get into situation where subscriber timing on + * initialization matter and later subscriber will only get latest device and will miss all + * previous devices. + */ + private val keyboardsChange: Flow<Pair<Collection<Int>, DeviceChange>> = conflatedCallbackFlow { - var connectedKeyboards = inputManager.inputDeviceIds.toSet() + var connectedDevices = inputManager.inputDeviceIds.toSet() val listener = object : InputManager.InputDeviceListener { override fun onInputDeviceAdded(deviceId: Int) { - connectedKeyboards = connectedKeyboards + deviceId - sendWithLogging(connectedKeyboards) + connectedDevices = connectedDevices + deviceId + sendWithLogging(connectedDevices to DeviceAdded(deviceId)) } override fun onInputDeviceChanged(deviceId: Int) = Unit override fun onInputDeviceRemoved(deviceId: Int) { - connectedKeyboards = connectedKeyboards - deviceId - sendWithLogging(connectedKeyboards) + connectedDevices = connectedDevices - deviceId + sendWithLogging(connectedDevices to DeviceRemoved) } } - sendWithLogging(connectedKeyboards) + sendWithLogging(connectedDevices to FreshStart) inputManager.registerInputDeviceListener(listener, /* handler= */ null) awaitClose { inputManager.unregisterInputDeviceListener(listener) } } + .map { (ids, change) -> ids.filter { id -> isPhysicalFullKeyboard(id) } to change } .shareIn( scope = applicationScope, started = SharingStarted.Lazily, replay = 1, ) + @FlowPreview + override val newlyConnectedKeyboard: Flow<Keyboard> = + keyboardsChange + .flatMapConcat { (devices, operation) -> + when (operation) { + FreshStart -> devices.asFlow() + is DeviceAdded -> flowOf(operation.deviceId) + is DeviceRemoved -> emptyFlow() + } + } + .mapNotNull { deviceIdToKeyboard(it) } + .flowOn(backgroundDispatcher) + + override val isAnyKeyboardConnected: Flow<Boolean> = + keyboardsChange + .map { (devices, _) -> devices.isNotEmpty() } + .distinctUntilChanged() + .flowOn(backgroundDispatcher) + private val backlightStateListener: Flow<KeyboardBacklightState> = conflatedCallbackFlow { val listener = KeyboardBacklightListener { _, state, isTriggeredByKeyPress -> if (isTriggeredByKeyPress) { @@ -90,11 +141,10 @@ constructor( awaitClose { inputManager.unregisterKeyboardBacklightListener(listener) } } - override val keyboardConnected: Flow<Boolean> = - connectedDeviceIds - .map { it.any { deviceId -> isPhysicalFullKeyboard(deviceId) } } - .distinctUntilChanged() - .flowOn(backgroundDispatcher) + private fun deviceIdToKeyboard(deviceId: Int): Keyboard? { + val device = inputManager.getInputDevice(deviceId) ?: return null + return Keyboard(device.vendorId, device.productId) + } override val backlight: Flow<BacklightModel> = backlightStateListener diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt index ec94cdec78f0..d7a0d5b5e062 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt @@ -47,14 +47,14 @@ class KeyboardBacklightInteractorTest : SysuiTestCase() { @Test fun emitsNull_whenKeyboardJustConnected() = runTest { val latest by collectLastValue(underTest.backlight) - keyboardRepository.setKeyboardConnected(true) + keyboardRepository.setIsAnyKeyboardConnected(true) assertThat(latest).isNull() } @Test fun emitsBacklight_whenKeyboardConnectedAndBacklightChanged() = runTest { - keyboardRepository.setKeyboardConnected(true) + keyboardRepository.setIsAnyKeyboardConnected(true) keyboardRepository.setBacklight(BacklightModel(1, 5)) assertThat(underTest.backlight.first()).isEqualTo(BacklightModel(1, 5)) @@ -63,10 +63,10 @@ class KeyboardBacklightInteractorTest : SysuiTestCase() { @Test fun emitsNull_afterKeyboardDisconnecting() = runTest { val latest by collectLastValue(underTest.backlight) - keyboardRepository.setKeyboardConnected(true) + keyboardRepository.setIsAnyKeyboardConnected(true) keyboardRepository.setBacklight(BacklightModel(1, 5)) - keyboardRepository.setKeyboardConnected(false) + keyboardRepository.setIsAnyKeyboardConnected(false) assertThat(latest).isNull() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogViewModelTest.kt index ec05d10b793c..1fec5a4d0f37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogViewModelTest.kt @@ -58,7 +58,7 @@ class BacklightDialogViewModelTest : SysuiTestCase() { KeyboardBacklightInteractor(keyboardRepository), accessibilityManagerWrapper ) - keyboardRepository.setKeyboardConnected(true) + keyboardRepository.setIsAnyKeyboardConnected(true) } @Test @@ -81,7 +81,7 @@ class BacklightDialogViewModelTest : SysuiTestCase() { @Test fun emitsNull_after5secDelay_fromLastBacklightChange() = runTest { val latest by collectLastValue(underTest.dialogContent) - keyboardRepository.setKeyboardConnected(true) + keyboardRepository.setIsAnyKeyboardConnected(true) keyboardRepository.setBacklight(BacklightModel(1, 5)) assertThat(latest).isEqualTo(BacklightDialogContentViewModel(1, 5)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt index fae637541028..4410e68e64aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt @@ -25,6 +25,8 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.FlowValue import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyboard.data.model.Keyboard import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable @@ -36,6 +38,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -78,7 +81,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun emitsDisconnected_ifNothingIsConnected() = testScope.runTest { - val initialState = underTest.keyboardConnected.first() + val initialState = underTest.isAnyKeyboardConnected.first() assertThat(initialState).isFalse() } @@ -86,7 +89,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { fun emitsConnected_ifKeyboardAlreadyConnectedAtTheStart() = testScope.runTest { whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID)) - val initialValue = underTest.keyboardConnected.first() + val initialValue = underTest.isAnyKeyboardConnected.first() assertThat(initialValue).isTrue() } @@ -94,7 +97,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { fun emitsConnected_whenNewPhysicalKeyboardConnects() = testScope.runTest { val deviceListener = captureDeviceListener() - val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) @@ -105,7 +108,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { fun emitsDisconnected_whenDeviceWithIdDoesNotExist() = testScope.runTest { val deviceListener = captureDeviceListener() - val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) deviceListener.onInputDeviceAdded(NULL_DEVICE_ID) assertThat(isKeyboardConnected).isFalse() @@ -115,7 +118,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { fun emitsDisconnected_whenKeyboardDisconnects() = testScope.runTest { val deviceListener = captureDeviceListener() - val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isTrue() @@ -125,7 +128,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { } private suspend fun captureDeviceListener(): InputManager.InputDeviceListener { - underTest.keyboardConnected.first() + underTest.isAnyKeyboardConnected.first() verify(inputManager).registerInputDeviceListener(deviceListenerCaptor.capture(), nullable()) return deviceListenerCaptor.value } @@ -134,7 +137,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { fun emitsDisconnected_whenVirtualOrNotFullKeyboardConnects() = testScope.runTest { val deviceListener = captureDeviceListener() - val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) deviceListener.onInputDeviceAdded(PHYSICAL_NOT_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isFalse() @@ -147,7 +150,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { fun emitsDisconnected_whenKeyboardDisconnectsAndWasAlreadyConnectedAtTheStart() = testScope.runTest { val deviceListener = captureDeviceListener() - val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID) assertThat(isKeyboardConnected).isFalse() @@ -157,7 +160,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { fun emitsConnected_whenAnotherDeviceDisconnects() = testScope.runTest { val deviceListener = captureDeviceListener() - val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) deviceListener.onInputDeviceRemoved(VIRTUAL_FULL_KEYBOARD_ID) @@ -169,7 +172,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { fun emitsConnected_whenOnePhysicalKeyboardDisconnectsButAnotherRemainsConnected() = testScope.runTest { val deviceListener = captureDeviceListener() - val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected) deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) @@ -205,7 +208,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun keyboardBacklightValuesNotPassed_fromBacklightListener_whenNotTriggeredByKeyPress() { - testScope.runTest() { + testScope.runTest { val backlight by collectLastValueImmediately(underTest.backlight) verify(inputManager) .registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture()) @@ -221,7 +224,7 @@ class KeyboardRepositoryTest : SysuiTestCase() { @Test fun passesKeyboardBacklightValues_fromBacklightListener_whenTriggeredByKeyPress() { - testScope.runTest() { + testScope.runTest { val backlight by collectLastValueImmediately(underTest.backlight) verify(inputManager) .registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture()) @@ -235,6 +238,86 @@ class KeyboardRepositoryTest : SysuiTestCase() { } } + @Test + fun passessAllKeyboards_thatWereAlreadyConnectedOnInitialization() { + testScope.runTest { + whenever(inputManager.inputDeviceIds) + .thenReturn( + intArrayOf( + PHYSICAL_FULL_KEYBOARD_ID, + ANOTHER_PHYSICAL_FULL_KEYBOARD_ID, + VIRTUAL_FULL_KEYBOARD_ID // not a physical keyboard - that's why result is 2 + ) + ) + val keyboards by collectValues(underTest.newlyConnectedKeyboard) + + assertThat(keyboards).hasSize(2) + } + } + + @Test + fun passesNewlyConnectedKeyboard() { + testScope.runTest { + val deviceListener = captureDeviceListener() + + deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + + assertThat(underTest.newlyConnectedKeyboard.first()) + .isEqualTo(Keyboard(VENDOR_ID, PRODUCT_ID)) + } + } + + @Test + fun emitsOnlyNewlyConnectedKeyboards() { + testScope.runTest { + whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID)) + underTest.newlyConnectedKeyboard.first() + verify(inputManager) + .registerInputDeviceListener(deviceListenerCaptor.capture(), nullable()) + val deviceListener = deviceListenerCaptor.value + + deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) + val keyboards by collectValues(underTest.newlyConnectedKeyboard) + + assertThat(keyboards).hasSize(1) + } + } + + @Test + fun stillEmitsNewKeyboardEvenIfFlowWasSubscribedAfterOtherFlows() { + testScope.runTest { + whenever(inputManager.inputDeviceIds) + .thenReturn( + intArrayOf( + PHYSICAL_FULL_KEYBOARD_ID, + ANOTHER_PHYSICAL_FULL_KEYBOARD_ID, + VIRTUAL_FULL_KEYBOARD_ID // not a physical keyboard - that's why result is 2 + ) + ) + collectLastValueImmediately(underTest.isAnyKeyboardConnected) + + // let's pretend second flow is subscribed after some delay + advanceTimeBy(1000) + val keyboards by collectValues(underTest.newlyConnectedKeyboard) + + assertThat(keyboards).hasSize(2) + } + } + + @Test + fun emitsKeyboardWhenItWasReconnected() { + testScope.runTest { + val deviceListener = captureDeviceListener() + val keyboards by collectValues(underTest.newlyConnectedKeyboard) + + deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID) + deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + + assertThat(keyboards).hasSize(2) + } + } + private fun KeyboardBacklightListener.onBacklightChanged( current: Int, max: Int, @@ -254,6 +337,9 @@ class KeyboardRepositoryTest : SysuiTestCase() { private const val ANOTHER_PHYSICAL_FULL_KEYBOARD_ID = 4 private const val NULL_DEVICE_ID = 5 + private const val VENDOR_ID = 99 + private const val PRODUCT_ID = 101 + private val INPUT_DEVICES_MAP: Map<Int, InputDevice> = mapOf( PHYSICAL_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = true), @@ -267,6 +353,8 @@ class KeyboardRepositoryTest : SysuiTestCase() { mock<InputDevice>().also { whenever(it.isVirtual).thenReturn(virtual) whenever(it.isFullKeyboard).thenReturn(fullKeyboard) + whenever(it.vendorId).thenReturn(VENDOR_ID) + whenever(it.productId).thenReturn(PRODUCT_ID) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt index 4e435462be50..b37cac1d36fd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyboard.data.repository +import com.android.systemui.keyboard.data.model.Keyboard import com.android.systemui.keyboard.shared.model.BacklightModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -24,18 +25,25 @@ import kotlinx.coroutines.flow.filterNotNull class FakeKeyboardRepository : KeyboardRepository { - private val _keyboardConnected = MutableStateFlow(false) - override val keyboardConnected: Flow<Boolean> = _keyboardConnected + private val _isAnyKeyboardConnected = MutableStateFlow(false) + override val isAnyKeyboardConnected: Flow<Boolean> = _isAnyKeyboardConnected private val _backlightState: MutableStateFlow<BacklightModel?> = MutableStateFlow(null) // filtering to make sure backlight doesn't have default initial value override val backlight: Flow<BacklightModel> = _backlightState.filterNotNull() + private val _newlyConnectedKeyboard: MutableStateFlow<Keyboard?> = MutableStateFlow(null) + override val newlyConnectedKeyboard: Flow<Keyboard> = _newlyConnectedKeyboard.filterNotNull() + fun setBacklight(state: BacklightModel) { _backlightState.value = state } - fun setKeyboardConnected(connected: Boolean) { - _keyboardConnected.value = connected + fun setIsAnyKeyboardConnected(connected: Boolean) { + _isAnyKeyboardConnected.value = connected + } + + fun setNewlyConnectedKeyboard(keyboard: Keyboard) { + _newlyConnectedKeyboard.value = keyboard } } |