• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 
18 package com.android.systemui.keyboard.data.repository
19 
20 import android.hardware.input.InputManager
21 import android.hardware.input.InputManager.KeyboardBacklightListener
22 import android.hardware.input.KeyboardBacklightState
23 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
24 import com.android.systemui.dagger.SysUISingleton
25 import com.android.systemui.dagger.qualifiers.Background
26 import com.android.systemui.inputdevice.data.repository.InputDeviceRepository
27 import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceAdded
28 import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.DeviceRemoved
29 import com.android.systemui.inputdevice.data.repository.InputDeviceRepository.FreshStart
30 import com.android.systemui.keyboard.data.model.Keyboard
31 import com.android.systemui.keyboard.shared.model.BacklightModel
32 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
33 import java.util.concurrent.Executor
34 import javax.inject.Inject
35 import kotlinx.coroutines.CoroutineDispatcher
36 import kotlinx.coroutines.FlowPreview
37 import kotlinx.coroutines.channels.SendChannel
38 import kotlinx.coroutines.channels.awaitClose
39 import kotlinx.coroutines.flow.Flow
40 import kotlinx.coroutines.flow.asFlow
41 import kotlinx.coroutines.flow.distinctUntilChanged
42 import kotlinx.coroutines.flow.emptyFlow
43 import kotlinx.coroutines.flow.filter
44 import kotlinx.coroutines.flow.flatMapConcat
45 import kotlinx.coroutines.flow.flowOf
46 import kotlinx.coroutines.flow.flowOn
47 import kotlinx.coroutines.flow.map
48 import kotlinx.coroutines.flow.mapNotNull
49 
50 /**
51  * Provides information about physical keyboard states. [CommandLineKeyboardRepository] can be
52  * useful command line-driven implementation during development.
53  */
54 interface KeyboardRepository {
55     /** Emits true if any physical keyboard is connected to the device, false otherwise. */
56     val isAnyKeyboardConnected: Flow<Boolean>
57 
58     /**
59      * Emits [Keyboard] object whenever new physical keyboard connects. When SysUI (re)starts it
60      * emits all currently connected keyboards
61      */
62     val newlyConnectedKeyboard: Flow<Keyboard>
63 
64     /** Emits set of currently connected keyboards */
65     val connectedKeyboards: Flow<Set<Keyboard>>
66 
67     /**
68      * Emits [BacklightModel] whenever user changes backlight level from keyboard press. Can only
69      * happen when physical keyboard is connected
70      */
71     val backlight: Flow<BacklightModel>
72 }
73 
74 @SysUISingleton
75 class KeyboardRepositoryImpl
76 @Inject
77 constructor(
78     @Background private val backgroundDispatcher: CoroutineDispatcher,
79     private val inputManager: InputManager,
80     inputDeviceRepository: InputDeviceRepository,
81 ) : KeyboardRepository {
82 
83     @FlowPreview
84     override val newlyConnectedKeyboard: Flow<Keyboard> =
85         inputDeviceRepository.deviceChange
devicesnull86             .flatMapConcat { (devices, operation) ->
87                 when (operation) {
88                     FreshStart -> devices.filter { id -> isPhysicalFullKeyboard(id) }.asFlow()
89                     is DeviceAdded -> {
90                         if (isPhysicalFullKeyboard(operation.deviceId)) flowOf(operation.deviceId)
91                         else emptyFlow()
92                     }
93                     is DeviceRemoved -> emptyFlow()
94                 }
95             }
<lambda>null96             .mapNotNull { deviceIdToKeyboard(it) }
97             .flowOn(backgroundDispatcher)
98 
99     override val connectedKeyboards: Flow<Set<Keyboard>> =
100         inputDeviceRepository.deviceChange
deviceIdsnull101             .map { (deviceIds, _) -> deviceIds }
<lambda>null102             .map { deviceIds -> deviceIds.filter { isPhysicalFullKeyboard(it) } }
103             .distinctUntilChanged()
<lambda>null104             .map { deviceIds -> deviceIds.mapNotNull { deviceIdToKeyboard(it) }.toSet() }
105 
106     override val isAnyKeyboardConnected: Flow<Boolean> =
107         inputDeviceRepository.deviceChange
idsnull108             .map { (ids, _) -> ids.any { id -> isPhysicalFullKeyboard(id) } }
109             .distinctUntilChanged()
110             .flowOn(backgroundDispatcher)
111 
<lambda>null112     private val backlightStateListener: Flow<KeyboardBacklightState> = conflatedCallbackFlow {
113         val listener = KeyboardBacklightListener { _, state, isTriggeredByKeyPress ->
114             if (isTriggeredByKeyPress) {
115                 sendWithLogging(state)
116             }
117         }
118         inputManager.registerKeyboardBacklightListener(Executor(Runnable::run), listener)
119         awaitClose { inputManager.unregisterKeyboardBacklightListener(listener) }
120     }
121 
deviceIdToKeyboardnull122     private fun deviceIdToKeyboard(deviceId: Int): Keyboard? {
123         val device = inputManager.getInputDevice(deviceId) ?: return null
124         return Keyboard(device.vendorId, device.productId)
125     }
126 
127     override val backlight: Flow<BacklightModel> =
128         backlightStateListener
<lambda>null129             .map { BacklightModel(it.brightnessLevel, it.maxBrightnessLevel) }
130             .flowOn(backgroundDispatcher)
131 
sendWithLoggingnull132     private fun <T> SendChannel<T>.sendWithLogging(element: T) {
133         trySendWithFailureLogging(element, TAG)
134     }
135 
isPhysicalFullKeyboardnull136     private fun isPhysicalFullKeyboard(deviceId: Int): Boolean {
137         val device = inputManager.getInputDevice(deviceId) ?: return false
138         return !device.isVirtual && device.isFullKeyboard
139     }
140 
141     companion object {
142         const val TAG = "KeyboardRepositoryImpl"
143     }
144 }
145