Merge "Move device connection logic to a separated class" into main
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt
new file mode 100644
index 0000000..3b161b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/data/repository/InputDeviceRepository.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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.inputdevice.data.repository
+
+import android.annotation.SuppressLint
+import android.hardware.input.InputManager
+import android.os.Handler
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+
+@SysUISingleton
+class InputDeviceRepository
+@Inject
+constructor(
+ @Background private val backgroundHandler: Handler,
+ @Background private val backgroundScope: CoroutineScope,
+ private val inputManager: InputManager
+) {
+
+ sealed interface DeviceChange
+
+ data class DeviceAdded(val deviceId: Int) : DeviceChange
+
+ data object DeviceRemoved : DeviceChange
+
+ data 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.
+ */
+ // TODO(b/351984587): Replace with StateFlow
+ @SuppressLint("SharedFlowCreation")
+ val deviceChange: Flow<Pair<Collection<Int>, DeviceChange>> =
+ conflatedCallbackFlow {
+ var connectedDevices = inputManager.inputDeviceIds.toSet()
+ val listener =
+ object : InputManager.InputDeviceListener {
+ override fun onInputDeviceAdded(deviceId: Int) {
+ connectedDevices = connectedDevices + deviceId
+ sendWithLogging(connectedDevices to DeviceAdded(deviceId))
+ }
+
+ override fun onInputDeviceChanged(deviceId: Int) = Unit
+
+ override fun onInputDeviceRemoved(deviceId: Int) {
+ connectedDevices = connectedDevices - deviceId
+ sendWithLogging(connectedDevices to DeviceRemoved)
+ }
+ }
+ sendWithLogging(connectedDevices to FreshStart)
+ inputManager.registerInputDeviceListener(listener, backgroundHandler)
+ awaitClose { inputManager.unregisterInputDeviceListener(listener) }
+ }
+ .shareIn(
+ scope = backgroundScope,
+ started = SharingStarted.Lazily,
+ replay = 1,
+ )
+
+ private fun <T> SendChannel<T>.sendWithLogging(element: T) {
+ trySendWithFailureLogging(element, TAG)
+ }
+
+ companion object {
+ const val TAG = "InputDeviceRepository"
+ }
+}
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 91d5280..817849c 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
@@ -21,21 +21,23 @@
import android.hardware.input.InputManager.KeyboardBacklightListener
import android.hardware.input.KeyboardBacklightState
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.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceAdded
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceChange
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceRemoved
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.FreshStart
import com.android.systemui.keyboard.data.model.Keyboard
import com.android.systemui.keyboard.shared.model.BacklightModel
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
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
@@ -44,7 +46,6 @@
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.flow.shareIn
/**
* Provides information about physical keyboard states. [CommandLineKeyboardRepository] can be
@@ -71,50 +72,15 @@
class KeyboardRepositoryImpl
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val inputManager: InputManager,
+ inputDeviceRepository: InputDeviceRepository
) : KeyboardRepository {
- 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 connectedDevices = inputManager.inputDeviceIds.toSet()
- val listener =
- object : InputManager.InputDeviceListener {
- override fun onInputDeviceAdded(deviceId: Int) {
- connectedDevices = connectedDevices + deviceId
- sendWithLogging(connectedDevices to DeviceAdded(deviceId))
- }
-
- override fun onInputDeviceChanged(deviceId: Int) = Unit
-
- override fun onInputDeviceRemoved(deviceId: Int) {
- connectedDevices = connectedDevices - deviceId
- sendWithLogging(connectedDevices to DeviceRemoved)
- }
- }
- 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,
- )
+ inputDeviceRepository.deviceChange.map { (ids, change) ->
+ ids.filter { id -> isPhysicalFullKeyboard(id) } to change
+ }
@FlowPreview
override val newlyConnectedKeyboard: Flow<Keyboard> =
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 53bcf86..361e768 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
@@ -20,6 +20,7 @@
import android.hardware.input.InputManager
import android.hardware.input.InputManager.KeyboardBacklightListener
import android.hardware.input.KeyboardBacklightState
+import android.testing.TestableLooper
import android.view.InputDevice
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -27,11 +28,13 @@
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.inputdevice.data.repository.InputDeviceRepository
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
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -53,6 +56,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@TestableLooper.RunWithLooper
@RunWith(AndroidJUnit4::class)
class KeyboardRepositoryTest : SysuiTestCase() {
@@ -63,6 +67,7 @@
private lateinit var underTest: KeyboardRepository
private lateinit var dispatcher: CoroutineDispatcher
+ private lateinit var inputDeviceRepo: InputDeviceRepository
private lateinit var testScope: TestScope
@Before
@@ -75,7 +80,9 @@
}
dispatcher = StandardTestDispatcher()
testScope = TestScope(dispatcher)
- underTest = KeyboardRepositoryImpl(testScope.backgroundScope, dispatcher, inputManager)
+ val handler = FakeHandler(TestableLooper.get(this).looper)
+ inputDeviceRepo = InputDeviceRepository(handler, testScope.backgroundScope, inputManager)
+ underTest = KeyboardRepositoryImpl(dispatcher, inputManager, inputDeviceRepo)
}
@Test
@@ -363,6 +370,7 @@
private val maxBrightnessLevel: Int
) : KeyboardBacklightState() {
override fun getBrightnessLevel() = brightnessLevel
+
override fun getMaxBrightnessLevel() = maxBrightnessLevel
}
}