Merge "Adding newlyConnectedKeyboard flow to KeyboardRepository" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
889b6b4c6e
@@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton
|
|||||||
import com.android.systemui.keyboard.data.repository.KeyboardRepository
|
import com.android.systemui.keyboard.data.repository.KeyboardRepository
|
||||||
import com.android.systemui.keyboard.shared.model.BacklightModel
|
import com.android.systemui.keyboard.shared.model.BacklightModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
@@ -34,8 +35,9 @@ constructor(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
/** Emits current backlight level as [BacklightModel] or null if keyboard is not connected */
|
/** Emits current backlight level as [BacklightModel] or null if keyboard is not connected */
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
val backlight: Flow<BacklightModel?> =
|
val backlight: Flow<BacklightModel?> =
|
||||||
keyboardRepository.keyboardConnected.flatMapLatest { connected ->
|
keyboardRepository.isAnyKeyboardConnected.flatMapLatest { connected ->
|
||||||
if (connected) keyboardRepository.backlight else flowOf(null)
|
if (connected) keyboardRepository.backlight else flowOf(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -25,22 +25,41 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
|
|||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Application
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
|
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 java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.channels.SendChannel
|
import kotlinx.coroutines.channels.SendChannel
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
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.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import kotlinx.coroutines.flow.shareIn
|
import kotlinx.coroutines.flow.shareIn
|
||||||
|
|
||||||
interface KeyboardRepository {
|
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>
|
val backlight: Flow<BacklightModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,33 +72,65 @@ constructor(
|
|||||||
private val inputManager: InputManager,
|
private val inputManager: InputManager,
|
||||||
) : KeyboardRepository {
|
) : 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 {
|
conflatedCallbackFlow {
|
||||||
var connectedKeyboards = inputManager.inputDeviceIds.toSet()
|
var connectedDevices = inputManager.inputDeviceIds.toSet()
|
||||||
val listener =
|
val listener =
|
||||||
object : InputManager.InputDeviceListener {
|
object : InputManager.InputDeviceListener {
|
||||||
override fun onInputDeviceAdded(deviceId: Int) {
|
override fun onInputDeviceAdded(deviceId: Int) {
|
||||||
connectedKeyboards = connectedKeyboards + deviceId
|
connectedDevices = connectedDevices + deviceId
|
||||||
sendWithLogging(connectedKeyboards)
|
sendWithLogging(connectedDevices to DeviceAdded(deviceId))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInputDeviceChanged(deviceId: Int) = Unit
|
override fun onInputDeviceChanged(deviceId: Int) = Unit
|
||||||
|
|
||||||
override fun onInputDeviceRemoved(deviceId: Int) {
|
override fun onInputDeviceRemoved(deviceId: Int) {
|
||||||
connectedKeyboards = connectedKeyboards - deviceId
|
connectedDevices = connectedDevices - deviceId
|
||||||
sendWithLogging(connectedKeyboards)
|
sendWithLogging(connectedDevices to DeviceRemoved)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sendWithLogging(connectedKeyboards)
|
sendWithLogging(connectedDevices to FreshStart)
|
||||||
inputManager.registerInputDeviceListener(listener, /* handler= */ null)
|
inputManager.registerInputDeviceListener(listener, /* handler= */ null)
|
||||||
awaitClose { inputManager.unregisterInputDeviceListener(listener) }
|
awaitClose { inputManager.unregisterInputDeviceListener(listener) }
|
||||||
}
|
}
|
||||||
|
.map { (ids, change) -> ids.filter { id -> isPhysicalFullKeyboard(id) } to change }
|
||||||
.shareIn(
|
.shareIn(
|
||||||
scope = applicationScope,
|
scope = applicationScope,
|
||||||
started = SharingStarted.Lazily,
|
started = SharingStarted.Lazily,
|
||||||
replay = 1,
|
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 {
|
private val backlightStateListener: Flow<KeyboardBacklightState> = conflatedCallbackFlow {
|
||||||
val listener = KeyboardBacklightListener { _, state, isTriggeredByKeyPress ->
|
val listener = KeyboardBacklightListener { _, state, isTriggeredByKeyPress ->
|
||||||
if (isTriggeredByKeyPress) {
|
if (isTriggeredByKeyPress) {
|
||||||
@@ -90,11 +141,10 @@ constructor(
|
|||||||
awaitClose { inputManager.unregisterKeyboardBacklightListener(listener) }
|
awaitClose { inputManager.unregisterKeyboardBacklightListener(listener) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override val keyboardConnected: Flow<Boolean> =
|
private fun deviceIdToKeyboard(deviceId: Int): Keyboard? {
|
||||||
connectedDeviceIds
|
val device = inputManager.getInputDevice(deviceId) ?: return null
|
||||||
.map { it.any { deviceId -> isPhysicalFullKeyboard(deviceId) } }
|
return Keyboard(device.vendorId, device.productId)
|
||||||
.distinctUntilChanged()
|
}
|
||||||
.flowOn(backgroundDispatcher)
|
|
||||||
|
|
||||||
override val backlight: Flow<BacklightModel> =
|
override val backlight: Flow<BacklightModel> =
|
||||||
backlightStateListener
|
backlightStateListener
|
||||||
|
|||||||
@@ -47,14 +47,14 @@ class KeyboardBacklightInteractorTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun emitsNull_whenKeyboardJustConnected() = runTest {
|
fun emitsNull_whenKeyboardJustConnected() = runTest {
|
||||||
val latest by collectLastValue(underTest.backlight)
|
val latest by collectLastValue(underTest.backlight)
|
||||||
keyboardRepository.setKeyboardConnected(true)
|
keyboardRepository.setIsAnyKeyboardConnected(true)
|
||||||
|
|
||||||
assertThat(latest).isNull()
|
assertThat(latest).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun emitsBacklight_whenKeyboardConnectedAndBacklightChanged() = runTest {
|
fun emitsBacklight_whenKeyboardConnectedAndBacklightChanged() = runTest {
|
||||||
keyboardRepository.setKeyboardConnected(true)
|
keyboardRepository.setIsAnyKeyboardConnected(true)
|
||||||
keyboardRepository.setBacklight(BacklightModel(1, 5))
|
keyboardRepository.setBacklight(BacklightModel(1, 5))
|
||||||
|
|
||||||
assertThat(underTest.backlight.first()).isEqualTo(BacklightModel(1, 5))
|
assertThat(underTest.backlight.first()).isEqualTo(BacklightModel(1, 5))
|
||||||
@@ -63,10 +63,10 @@ class KeyboardBacklightInteractorTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun emitsNull_afterKeyboardDisconnecting() = runTest {
|
fun emitsNull_afterKeyboardDisconnecting() = runTest {
|
||||||
val latest by collectLastValue(underTest.backlight)
|
val latest by collectLastValue(underTest.backlight)
|
||||||
keyboardRepository.setKeyboardConnected(true)
|
keyboardRepository.setIsAnyKeyboardConnected(true)
|
||||||
keyboardRepository.setBacklight(BacklightModel(1, 5))
|
keyboardRepository.setBacklight(BacklightModel(1, 5))
|
||||||
|
|
||||||
keyboardRepository.setKeyboardConnected(false)
|
keyboardRepository.setIsAnyKeyboardConnected(false)
|
||||||
|
|
||||||
assertThat(latest).isNull()
|
assertThat(latest).isNull()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class BacklightDialogViewModelTest : SysuiTestCase() {
|
|||||||
KeyboardBacklightInteractor(keyboardRepository),
|
KeyboardBacklightInteractor(keyboardRepository),
|
||||||
accessibilityManagerWrapper
|
accessibilityManagerWrapper
|
||||||
)
|
)
|
||||||
keyboardRepository.setKeyboardConnected(true)
|
keyboardRepository.setIsAnyKeyboardConnected(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -81,7 +81,7 @@ class BacklightDialogViewModelTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun emitsNull_after5secDelay_fromLastBacklightChange() = runTest {
|
fun emitsNull_after5secDelay_fromLastBacklightChange() = runTest {
|
||||||
val latest by collectLastValue(underTest.dialogContent)
|
val latest by collectLastValue(underTest.dialogContent)
|
||||||
keyboardRepository.setKeyboardConnected(true)
|
keyboardRepository.setIsAnyKeyboardConnected(true)
|
||||||
|
|
||||||
keyboardRepository.setBacklight(BacklightModel(1, 5))
|
keyboardRepository.setBacklight(BacklightModel(1, 5))
|
||||||
assertThat(latest).isEqualTo(BacklightDialogContentViewModel(1, 5))
|
assertThat(latest).isEqualTo(BacklightDialogContentViewModel(1, 5))
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import androidx.test.filters.SmallTest
|
|||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.coroutines.FlowValue
|
import com.android.systemui.coroutines.FlowValue
|
||||||
import com.android.systemui.coroutines.collectLastValue
|
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.any
|
||||||
import com.android.systemui.util.mockito.mock
|
import com.android.systemui.util.mockito.mock
|
||||||
import com.android.systemui.util.mockito.nullable
|
import com.android.systemui.util.mockito.nullable
|
||||||
@@ -36,6 +38,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.advanceTimeBy
|
||||||
import kotlinx.coroutines.test.runCurrent
|
import kotlinx.coroutines.test.runCurrent
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -78,7 +81,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun emitsDisconnected_ifNothingIsConnected() =
|
fun emitsDisconnected_ifNothingIsConnected() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val initialState = underTest.keyboardConnected.first()
|
val initialState = underTest.isAnyKeyboardConnected.first()
|
||||||
assertThat(initialState).isFalse()
|
assertThat(initialState).isFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +89,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
fun emitsConnected_ifKeyboardAlreadyConnectedAtTheStart() =
|
fun emitsConnected_ifKeyboardAlreadyConnectedAtTheStart() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID))
|
whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID))
|
||||||
val initialValue = underTest.keyboardConnected.first()
|
val initialValue = underTest.isAnyKeyboardConnected.first()
|
||||||
assertThat(initialValue).isTrue()
|
assertThat(initialValue).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +97,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
fun emitsConnected_whenNewPhysicalKeyboardConnects() =
|
fun emitsConnected_whenNewPhysicalKeyboardConnects() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val deviceListener = captureDeviceListener()
|
val deviceListener = captureDeviceListener()
|
||||||
val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
|
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
|
||||||
|
|
||||||
deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
|
deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
|
||||||
|
|
||||||
@@ -105,7 +108,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
fun emitsDisconnected_whenDeviceWithIdDoesNotExist() =
|
fun emitsDisconnected_whenDeviceWithIdDoesNotExist() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val deviceListener = captureDeviceListener()
|
val deviceListener = captureDeviceListener()
|
||||||
val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
|
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
|
||||||
|
|
||||||
deviceListener.onInputDeviceAdded(NULL_DEVICE_ID)
|
deviceListener.onInputDeviceAdded(NULL_DEVICE_ID)
|
||||||
assertThat(isKeyboardConnected).isFalse()
|
assertThat(isKeyboardConnected).isFalse()
|
||||||
@@ -115,7 +118,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
fun emitsDisconnected_whenKeyboardDisconnects() =
|
fun emitsDisconnected_whenKeyboardDisconnects() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val deviceListener = captureDeviceListener()
|
val deviceListener = captureDeviceListener()
|
||||||
val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
|
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
|
||||||
|
|
||||||
deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
|
deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
|
||||||
assertThat(isKeyboardConnected).isTrue()
|
assertThat(isKeyboardConnected).isTrue()
|
||||||
@@ -125,7 +128,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun captureDeviceListener(): InputManager.InputDeviceListener {
|
private suspend fun captureDeviceListener(): InputManager.InputDeviceListener {
|
||||||
underTest.keyboardConnected.first()
|
underTest.isAnyKeyboardConnected.first()
|
||||||
verify(inputManager).registerInputDeviceListener(deviceListenerCaptor.capture(), nullable())
|
verify(inputManager).registerInputDeviceListener(deviceListenerCaptor.capture(), nullable())
|
||||||
return deviceListenerCaptor.value
|
return deviceListenerCaptor.value
|
||||||
}
|
}
|
||||||
@@ -134,7 +137,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
fun emitsDisconnected_whenVirtualOrNotFullKeyboardConnects() =
|
fun emitsDisconnected_whenVirtualOrNotFullKeyboardConnects() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val deviceListener = captureDeviceListener()
|
val deviceListener = captureDeviceListener()
|
||||||
val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
|
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
|
||||||
|
|
||||||
deviceListener.onInputDeviceAdded(PHYSICAL_NOT_FULL_KEYBOARD_ID)
|
deviceListener.onInputDeviceAdded(PHYSICAL_NOT_FULL_KEYBOARD_ID)
|
||||||
assertThat(isKeyboardConnected).isFalse()
|
assertThat(isKeyboardConnected).isFalse()
|
||||||
@@ -147,7 +150,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
fun emitsDisconnected_whenKeyboardDisconnectsAndWasAlreadyConnectedAtTheStart() =
|
fun emitsDisconnected_whenKeyboardDisconnectsAndWasAlreadyConnectedAtTheStart() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val deviceListener = captureDeviceListener()
|
val deviceListener = captureDeviceListener()
|
||||||
val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
|
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
|
||||||
|
|
||||||
deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID)
|
deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID)
|
||||||
assertThat(isKeyboardConnected).isFalse()
|
assertThat(isKeyboardConnected).isFalse()
|
||||||
@@ -157,7 +160,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
fun emitsConnected_whenAnotherDeviceDisconnects() =
|
fun emitsConnected_whenAnotherDeviceDisconnects() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val deviceListener = captureDeviceListener()
|
val deviceListener = captureDeviceListener()
|
||||||
val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
|
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
|
||||||
|
|
||||||
deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
|
deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
|
||||||
deviceListener.onInputDeviceRemoved(VIRTUAL_FULL_KEYBOARD_ID)
|
deviceListener.onInputDeviceRemoved(VIRTUAL_FULL_KEYBOARD_ID)
|
||||||
@@ -169,7 +172,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
fun emitsConnected_whenOnePhysicalKeyboardDisconnectsButAnotherRemainsConnected() =
|
fun emitsConnected_whenOnePhysicalKeyboardDisconnectsButAnotherRemainsConnected() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
val deviceListener = captureDeviceListener()
|
val deviceListener = captureDeviceListener()
|
||||||
val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
|
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
|
||||||
|
|
||||||
deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
|
deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
|
||||||
deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
|
deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
|
||||||
@@ -205,7 +208,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun keyboardBacklightValuesNotPassed_fromBacklightListener_whenNotTriggeredByKeyPress() {
|
fun keyboardBacklightValuesNotPassed_fromBacklightListener_whenNotTriggeredByKeyPress() {
|
||||||
testScope.runTest() {
|
testScope.runTest {
|
||||||
val backlight by collectLastValueImmediately(underTest.backlight)
|
val backlight by collectLastValueImmediately(underTest.backlight)
|
||||||
verify(inputManager)
|
verify(inputManager)
|
||||||
.registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture())
|
.registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture())
|
||||||
@@ -221,7 +224,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun passesKeyboardBacklightValues_fromBacklightListener_whenTriggeredByKeyPress() {
|
fun passesKeyboardBacklightValues_fromBacklightListener_whenTriggeredByKeyPress() {
|
||||||
testScope.runTest() {
|
testScope.runTest {
|
||||||
val backlight by collectLastValueImmediately(underTest.backlight)
|
val backlight by collectLastValueImmediately(underTest.backlight)
|
||||||
verify(inputManager)
|
verify(inputManager)
|
||||||
.registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture())
|
.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(
|
private fun KeyboardBacklightListener.onBacklightChanged(
|
||||||
current: Int,
|
current: Int,
|
||||||
max: Int,
|
max: Int,
|
||||||
@@ -254,6 +337,9 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
private const val ANOTHER_PHYSICAL_FULL_KEYBOARD_ID = 4
|
private const val ANOTHER_PHYSICAL_FULL_KEYBOARD_ID = 4
|
||||||
private const val NULL_DEVICE_ID = 5
|
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> =
|
private val INPUT_DEVICES_MAP: Map<Int, InputDevice> =
|
||||||
mapOf(
|
mapOf(
|
||||||
PHYSICAL_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = true),
|
PHYSICAL_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = true),
|
||||||
@@ -267,6 +353,8 @@ class KeyboardRepositoryTest : SysuiTestCase() {
|
|||||||
mock<InputDevice>().also {
|
mock<InputDevice>().also {
|
||||||
whenever(it.isVirtual).thenReturn(virtual)
|
whenever(it.isVirtual).thenReturn(virtual)
|
||||||
whenever(it.isFullKeyboard).thenReturn(fullKeyboard)
|
whenever(it.isFullKeyboard).thenReturn(fullKeyboard)
|
||||||
|
whenever(it.vendorId).thenReturn(VENDOR_ID)
|
||||||
|
whenever(it.productId).thenReturn(PRODUCT_ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.keyboard.data.repository
|
package com.android.systemui.keyboard.data.repository
|
||||||
|
|
||||||
|
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.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -24,18 +25,25 @@ import kotlinx.coroutines.flow.filterNotNull
|
|||||||
|
|
||||||
class FakeKeyboardRepository : KeyboardRepository {
|
class FakeKeyboardRepository : KeyboardRepository {
|
||||||
|
|
||||||
private val _keyboardConnected = MutableStateFlow(false)
|
private val _isAnyKeyboardConnected = MutableStateFlow(false)
|
||||||
override val keyboardConnected: Flow<Boolean> = _keyboardConnected
|
override val isAnyKeyboardConnected: Flow<Boolean> = _isAnyKeyboardConnected
|
||||||
|
|
||||||
private val _backlightState: MutableStateFlow<BacklightModel?> = MutableStateFlow(null)
|
private val _backlightState: MutableStateFlow<BacklightModel?> = MutableStateFlow(null)
|
||||||
// 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)
|
||||||
|
override val newlyConnectedKeyboard: Flow<Keyboard> = _newlyConnectedKeyboard.filterNotNull()
|
||||||
|
|
||||||
fun setBacklight(state: BacklightModel) {
|
fun setBacklight(state: BacklightModel) {
|
||||||
_backlightState.value = state
|
_backlightState.value = state
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setKeyboardConnected(connected: Boolean) {
|
fun setIsAnyKeyboardConnected(connected: Boolean) {
|
||||||
_keyboardConnected.value = connected
|
_isAnyKeyboardConnected.value = connected
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setNewlyConnectedKeyboard(keyboard: Keyboard) {
|
||||||
|
_newlyConnectedKeyboard.value = keyboard
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user