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