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 package com.android.systemui.display.data.repository
18 
19 import android.annotation.SuppressLint
20 import android.hardware.display.DisplayManager
21 import android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED
22 import android.hardware.display.DisplayManager.DisplayListener
23 import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_ADDED
24 import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
25 import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
26 import android.os.Handler
27 import android.util.Log
28 import android.view.Display
29 import com.android.app.tracing.FlowTracing.traceEach
30 import com.android.app.tracing.traceSection
31 import com.android.systemui.Flags
32 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
33 import com.android.systemui.dagger.SysUISingleton
34 import com.android.systemui.dagger.qualifiers.Background
35 import com.android.systemui.display.data.DisplayEvent
36 import com.android.systemui.util.Compile
37 import javax.inject.Inject
38 import kotlinx.coroutines.CoroutineDispatcher
39 import kotlinx.coroutines.CoroutineScope
40 import kotlinx.coroutines.channels.awaitClose
41 import kotlinx.coroutines.flow.Flow
42 import kotlinx.coroutines.flow.MutableStateFlow
43 import kotlinx.coroutines.flow.SharingStarted
44 import kotlinx.coroutines.flow.StateFlow
45 import kotlinx.coroutines.flow.combine
46 import kotlinx.coroutines.flow.distinctUntilChanged
47 import kotlinx.coroutines.flow.filterIsInstance
48 import kotlinx.coroutines.flow.flowOn
49 import kotlinx.coroutines.flow.map
50 import kotlinx.coroutines.flow.onEach
51 import kotlinx.coroutines.flow.onStart
52 import kotlinx.coroutines.flow.scan
53 import kotlinx.coroutines.flow.shareIn
54 import kotlinx.coroutines.flow.stateIn
55 
56 /** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
57 interface DisplayRepository {
58     /** Display change event indicating a change to the given displayId has occurred. */
59     val displayChangeEvent: Flow<Int>
60 
61     /** Display addition event indicating a new display has been added. */
62     val displayAdditionEvent: Flow<Display?>
63 
64     /** Provides the current set of displays. */
65     val displays: Flow<Set<Display>>
66 
67     /**
68      * Pending display id that can be enabled/disabled.
69      *
70      * When `null`, it means there is no pending display waiting to be enabled.
71      */
72     val pendingDisplay: Flow<PendingDisplay?>
73 
74     /** Whether the default display is currently off. */
75     val defaultDisplayOff: Flow<Boolean>
76 
77     /** Represents a connected display that has not been enabled yet. */
78     interface PendingDisplay {
79         /** Id of the pending display. */
80         val id: Int
81 
82         /** Enables the display, making it available to the system. */
83         suspend fun enable()
84 
85         /**
86          * Ignores the pending display. When called, this specific display id doesn't appear as
87          * pending anymore until the display is disconnected and reconnected again.
88          */
89         suspend fun ignore()
90 
91         /** Disables the display, making it unavailable to the system. */
92         suspend fun disable()
93     }
94 }
95 
96 @SysUISingleton
97 @SuppressLint("SharedFlowCreation")
98 class DisplayRepositoryImpl
99 @Inject
100 constructor(
101     private val displayManager: DisplayManager,
102     @Background backgroundHandler: Handler,
103     @Background bgApplicationScope: CoroutineScope,
104     @Background backgroundCoroutineDispatcher: CoroutineDispatcher
105 ) : DisplayRepository {
106     private val allDisplayEvents: Flow<DisplayEvent> =
<lambda>null107         conflatedCallbackFlow {
108                 val callback =
109                     object : DisplayListener {
110                         override fun onDisplayAdded(displayId: Int) {
111                             trySend(DisplayEvent.Added(displayId))
112                         }
113 
114                         override fun onDisplayRemoved(displayId: Int) {
115                             trySend(DisplayEvent.Removed(displayId))
116                         }
117 
118                         override fun onDisplayChanged(displayId: Int) {
119                             trySend(DisplayEvent.Changed(displayId))
120                         }
121                     }
122                 displayManager.registerDisplayListener(
123                     callback,
124                     backgroundHandler,
125                     EVENT_FLAG_DISPLAY_ADDED or
126                         EVENT_FLAG_DISPLAY_CHANGED or
127                         EVENT_FLAG_DISPLAY_REMOVED,
128                 )
129                 awaitClose { displayManager.unregisterDisplayListener(callback) }
130             }
<lambda>null131             .onStart { emit(DisplayEvent.Changed(Display.DEFAULT_DISPLAY)) }
132             .debugLog("allDisplayEvents")
133             .flowOn(backgroundCoroutineDispatcher)
134 
135     override val displayChangeEvent: Flow<Int> =
eventnull136         allDisplayEvents.filterIsInstance<DisplayEvent.Changed>().map { event -> event.displayId }
137 
138     override val displayAdditionEvent: Flow<Display?> =
<lambda>null139         allDisplayEvents.filterIsInstance<DisplayEvent.Added>().map { getDisplay(it.displayId) }
140 
141     private val oldEnabledDisplays: Flow<Set<Display>> =
142         allDisplayEvents
<lambda>null143             .map { getDisplays() }
144             .shareIn(bgApplicationScope, started = SharingStarted.WhileSubscribed(), replay = 1)
145 
146     /** Propagate to the listeners only enabled displays */
147     private val enabledDisplayIds: Flow<Set<Int>> =
148         if (Flags.enableEfficientDisplayRepository()) {
149                 allDisplayEvents
previousIdsnull150                     .scan(initial = emptySet()) { previousIds: Set<Int>, event: DisplayEvent ->
151                         val id = event.displayId
152                         when (event) {
153                             is DisplayEvent.Removed -> previousIds - id
154                             is DisplayEvent.Added,
155                             is DisplayEvent.Changed -> previousIds + id
156                         }
157                     }
158                     .shareIn(
159                         bgApplicationScope,
160                         started = SharingStarted.WhileSubscribed(),
161                         replay = 1
162                     )
163             } else {
enabledDisplaysSetnull164                 oldEnabledDisplays.map { enabledDisplaysSet ->
165                     enabledDisplaysSet.map { it.displayId }.toSet()
166                 }
167             }
168             .debugLog("enabledDisplayIds")
169 
170     /**
171      * Represents displays that went though the [DisplayListener.onDisplayAdded] callback.
172      *
173      * Those are commonly the ones provided by [DisplayManager.getDisplays] by default.
174      */
175     private val enabledDisplays: Flow<Set<Display>> =
176         if (Flags.enableEfficientDisplayRepository()) {
177             enabledDisplayIds
displayIdnull178                 .mapElementsLazily { displayId -> getDisplay(displayId) }
179                 .flowOn(backgroundCoroutineDispatcher)
180                 .debugLog("enabledDisplayIds")
181         } else {
182             oldEnabledDisplays
183         }
184 
185     /**
186      * Represents displays that went though the [DisplayListener.onDisplayAdded] callback.
187      *
188      * Those are commonly the ones provided by [DisplayManager.getDisplays] by default.
189      */
190     override val displays: Flow<Set<Display>> = enabledDisplays
191 
getDisplaysnull192     private fun getDisplays(): Set<Display> =
193         traceSection("$TAG#getDisplays()") { displayManager.displays?.toSet() ?: emptySet() }
194 
195     private val _ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet())
196     private val ignoredDisplayIds: Flow<Set<Int>> = _ignoredDisplayIds.debugLog("ignoredDisplayIds")
197 
getInitialConnectedDisplaysnull198     private fun getInitialConnectedDisplays(): Set<Int> =
199         traceSection("$TAG#getInitialConnectedDisplays") {
200             displayManager
201                 .getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
202                 .map { it.displayId }
203                 .toSet()
204                 .also {
205                     if (DEBUG) {
206                         Log.d(TAG, "getInitialConnectedDisplays: $it")
207                     }
208                 }
209         }
210 
211     /* keeps connected displays until they are disconnected. */
212     private val connectedDisplayIds: StateFlow<Set<Int>> =
<lambda>null213         conflatedCallbackFlow {
214                 val connectedIds = getInitialConnectedDisplays().toMutableSet()
215                 val callback =
216                     object : DisplayConnectionListener {
217                         override fun onDisplayConnected(id: Int) {
218                             if (DEBUG) {
219                                 Log.d(TAG, "display with id=$id connected.")
220                             }
221                             connectedIds += id
222                             _ignoredDisplayIds.value -= id
223                             trySend(connectedIds.toSet())
224                         }
225 
226                         override fun onDisplayDisconnected(id: Int) {
227                             connectedIds -= id
228                             if (DEBUG) {
229                                 Log.d(TAG, "display with id=$id disconnected.")
230                             }
231                             _ignoredDisplayIds.value -= id
232                             trySend(connectedIds.toSet())
233                         }
234                     }
235                 trySend(connectedIds.toSet())
236                 displayManager.registerDisplayListener(
237                     callback,
238                     backgroundHandler,
239                     DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
240                 )
241                 awaitClose { displayManager.unregisterDisplayListener(callback) }
242             }
243             .distinctUntilChanged()
244             .debugLog("connectedDisplayIds")
245             .stateIn(
246                 bgApplicationScope,
247                 started = SharingStarted.WhileSubscribed(),
248                 // The initial value is set to empty, but connected displays are gathered as soon as
249                 // the flow starts being collected. This is to ensure the call to get displays (an
250                 // IPC) happens in the background instead of when this object
251                 // is instantiated.
252                 initialValue = emptySet()
253             )
254 
255     private val connectedExternalDisplayIds: Flow<Set<Int>> =
256         connectedDisplayIds
connectedDisplayIdsnull257             .map { connectedDisplayIds ->
258                 traceSection("$TAG#filteringExternalDisplays") {
259                     connectedDisplayIds
260                         .filter { id -> getDisplayType(id) == Display.TYPE_EXTERNAL }
261                         .toSet()
262                 }
263             }
264             .flowOn(backgroundCoroutineDispatcher)
265             .debugLog("connectedExternalDisplayIds")
266 
getDisplayTypenull267     private fun getDisplayType(displayId: Int): Int? =
268         traceSection("$TAG#getDisplayType") { displayManager.getDisplay(displayId)?.type }
269 
getDisplaynull270     private fun getDisplay(displayId: Int): Display? =
271         traceSection("$TAG#getDisplay") { displayManager.getDisplay(displayId) }
272 
273     /**
274      * Pending displays are the ones connected, but not enabled and not ignored.
275      *
276      * A connected display is ignored after the user makes the decision to use it or not. For now,
277      * the initial decision from the user is final and not reversible.
278      */
279     private val pendingDisplayIds: Flow<Set<Int>> =
280         combine(enabledDisplayIds, connectedExternalDisplayIds, ignoredDisplayIds) {
enabledDisplaysIdsnull281                 enabledDisplaysIds,
282                 connectedExternalDisplayIds,
283                 ignoredDisplayIds ->
284                 if (DEBUG) {
285                     Log.d(
286                         TAG,
287                         "combining enabled=$enabledDisplaysIds, " +
288                             "connectedExternalDisplayIds=$connectedExternalDisplayIds, " +
289                             "ignored=$ignoredDisplayIds"
290                     )
291                 }
292                 connectedExternalDisplayIds - enabledDisplaysIds - ignoredDisplayIds
293             }
294             .debugLog("allPendingDisplayIds")
295 
296     /** Which display id should be enabled among the pending ones. */
297     private val pendingDisplayId: Flow<Int?> =
<lambda>null298         pendingDisplayIds.map { it.maxOrNull() }.distinctUntilChanged().debugLog("pendingDisplayId")
299 
300     override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?> =
301         pendingDisplayId
displayIdnull302             .map { displayId ->
303                 val id = displayId ?: return@map null
304                 object : DisplayRepository.PendingDisplay {
305                     override val id = id
306 
307                     override suspend fun enable() {
308                         traceSection("DisplayRepository#enable($id)") {
309                             if (DEBUG) {
310                                 Log.d(TAG, "Enabling display with id=$id")
311                             }
312                             displayManager.enableConnectedDisplay(id)
313                         }
314                         // After the display has been enabled, it is automatically ignored.
315                         ignore()
316                     }
317 
318                     override suspend fun ignore() {
319                         traceSection("DisplayRepository#ignore($id)") {
320                             _ignoredDisplayIds.value += id
321                         }
322                     }
323 
324                     override suspend fun disable() {
325                         ignore()
326                         traceSection("DisplayRepository#disable($id)") {
327                             if (DEBUG) {
328                                 Log.d(TAG, "Disabling display with id=$id")
329                             }
330                             displayManager.disableConnectedDisplay(id)
331                         }
332                     }
333                 }
334             }
335             .debugLog("pendingDisplay")
336 
337     override val defaultDisplayOff: Flow<Boolean> =
338         displays
<lambda>null339             .map { displays -> displays.firstOrNull { it.displayId == Display.DEFAULT_DISPLAY } }
<lambda>null340             .map { it?.state == Display.STATE_OFF }
341 
debugLognull342     private fun <T> Flow<T>.debugLog(flowName: String): Flow<T> {
343         return if (DEBUG) {
344             traceEach(flowName, logcat = true, traceEmissionCount = true)
345         } else {
346             this
347         }
348     }
349 
350     /**
351      * Maps a set of T to a set of V, minimizing the number of `createValue` calls taking into
352      * account the diff between each root flow emission.
353      *
354      * This is needed to minimize the number of [getDisplay] in this class. Note that if the
355      * [createValue] returns a null element, it will not be added in the output set.
356      */
mapElementsLazilynull357     private fun <T, V> Flow<Set<T>>.mapElementsLazily(createValue: (T) -> V?): Flow<Set<V>> {
358         var initialSet = emptySet<T>()
359         val currentMap = mutableMapOf<T, V>()
360         var resultSet = emptySet<V>()
361         return onEach { currentSet ->
362                 val removed = initialSet - currentSet
363                 val added = currentSet - initialSet
364                 if (added.isNotEmpty() || removed.isNotEmpty()) {
365                     added.forEach { key: T -> createValue(key)?.let { currentMap[key] = it } }
366                     removed.forEach { key: T -> currentMap.remove(key) }
367                     resultSet = currentMap.values.toSet() // Creates a **copy** of values
368                 }
369                 initialSet = currentSet
370             }
371             .map { resultSet }
372     }
373 
374     private companion object {
375         const val TAG = "DisplayRepository"
376         val DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG
377     }
378 }
379 
380 /** Used to provide default implementations for all methods. */
381 private interface DisplayConnectionListener : DisplayListener {
382 
onDisplayConnectednull383     override fun onDisplayConnected(id: Int) {}
384 
onDisplayDisconnectednull385     override fun onDisplayDisconnected(id: Int) {}
386 
onDisplayAddednull387     override fun onDisplayAdded(id: Int) {}
388 
onDisplayRemovednull389     override fun onDisplayRemoved(id: Int) {}
390 
onDisplayChangednull391     override fun onDisplayChanged(id: Int) {}
392 }
393