1 /* <lambda>null2 * Copyright (C) 2025 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.app.displaylib 18 19 import android.util.Log 20 import android.view.Display 21 import com.android.app.tracing.coroutines.flow.stateInTraced 22 import com.android.app.tracing.coroutines.launchTraced as launch 23 import com.android.app.tracing.traceSection 24 import dagger.assisted.Assisted 25 import dagger.assisted.AssistedFactory 26 import dagger.assisted.AssistedInject 27 import java.util.concurrent.ConcurrentHashMap 28 import javax.inject.Qualifier 29 import kotlinx.coroutines.CoroutineScope 30 import kotlinx.coroutines.flow.SharingStarted 31 import kotlinx.coroutines.flow.StateFlow 32 import kotlinx.coroutines.flow.collectLatest 33 import kotlinx.coroutines.flow.combine 34 35 /** 36 * Used to create instances of type `T` for a specific display. 37 * 38 * This is useful for resources or objects that need to be managed independently for each connected 39 * display (e.g., UI state, rendering contexts, or display-specific configurations). 40 * 41 * Note that in most cases this can be implemented by a simple `@AssistedFactory` with `displayId` 42 * parameter 43 * 44 * ```kotlin 45 * class SomeType @AssistedInject constructor(@Assisted displayId: Int,..) 46 * @AssistedFactory 47 * interface Factory { 48 * fun create(displayId: Int): SomeType 49 * } 50 * } 51 * ``` 52 * 53 * Then it can be used to create a [PerDisplayRepository] as follows: 54 * ```kotlin 55 * // Injected: 56 * val repositoryFactory: PerDisplayRepositoryImpl.Factory 57 * val instanceFactory: PerDisplayRepositoryImpl.Factory 58 * // repository creation: 59 * repositoryFactory.create(instanceFactory::create) 60 * ``` 61 * 62 * @see PerDisplayRepository For how to retrieve and manage instances created by this factory. 63 */ 64 fun interface PerDisplayInstanceProvider<T> { 65 /** Creates an instance for a display. */ 66 fun createInstance(displayId: Int): T? 67 } 68 69 /** 70 * Extends [PerDisplayInstanceProvider], adding support for destroying the instance. 71 * 72 * This is useful for releasing resources associated with a display when it is disconnected or when 73 * the per-display instance is no longer needed. 74 */ 75 interface PerDisplayInstanceProviderWithTeardown<T> : PerDisplayInstanceProvider<T> { 76 /** Destroys a previously created instance of `T` forever. */ destroyInstancenull77 fun destroyInstance(instance: T) 78 } 79 80 /** 81 * Provides access to per-display instances of type `T`. 82 * 83 * Acts as a repository, managing the caching and retrieval of instances created by a 84 * [PerDisplayInstanceProvider]. It ensures that only one instance of `T` exists per display ID. 85 */ 86 interface PerDisplayRepository<T> { 87 /** Gets the cached instance or create a new one for a given display. */ 88 operator fun get(displayId: Int): T? 89 90 /** Debug name for this repository, mainly for tracing and logging. */ 91 val debugName: String 92 93 /** 94 * Callback to run when a given repository is initialized. 95 * 96 * This allows the caller to perform custom logic when the repository is ready to be used, e.g. 97 * register to dumpManager. 98 * 99 * Note that the instance is *leaked* outside of this class, so it should only be done when 100 * repository is meant to live as long as the caller. In systemUI this is ok because the 101 * repository lives as long as the process itself. 102 */ 103 fun interface InitCallback { 104 fun onInit(debugName: String, instance: Any) 105 } 106 } 107 108 /** Qualifier for [CoroutineScope] used for displaylib background tasks. */ 109 @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class DisplayLibBackground 110 111 /** 112 * Default implementation of [PerDisplayRepository]. 113 * 114 * This class manages a cache of per-display instances of type `T`, creating them using a provided 115 * [PerDisplayInstanceProvider] and optionally tearing them down using a 116 * [PerDisplayInstanceProviderWithTeardown] when based on [lifecycleManager]. 117 * 118 * An instance will be destroyed when either 119 * - The display is not connected anymore 120 * - or based on [lifecycleManager]. If no lifecycle manager is provided, instances are destroyed 121 * when the display is disconnected. 122 * 123 * [DisplayInstanceLifecycleManager] can decide to delete instances for a display even before it is 124 * disconnected. An example of usecase for it, is to delete instances when screen decorations are 125 * removed. 126 * 127 * Note that this is a [PerDisplayStoreImpl] 2.0 that doesn't require [CoreStartable] bindings, 128 * providing all args in the constructor. 129 */ 130 class PerDisplayInstanceRepositoryImpl<T> 131 @AssistedInject 132 constructor( 133 @Assisted override val debugName: String, 134 @Assisted private val instanceProvider: PerDisplayInstanceProvider<T>, 135 @Assisted lifecycleManager: DisplayInstanceLifecycleManager? = null, 136 @DisplayLibBackground bgApplicationScope: CoroutineScope, 137 private val displayRepository: DisplayRepository, 138 private val initCallback: PerDisplayRepository.InitCallback, 139 ) : PerDisplayRepository<T> { 140 141 private val perDisplayInstances = ConcurrentHashMap<Int, T?>() 142 143 private val allowedDisplays: StateFlow<Set<Int>> = 144 if (lifecycleManager == null) { 145 displayRepository.displayIds 146 } else { 147 // If there is a lifecycle manager, we still consider the smallest subset between 148 // the ones connected and the ones from the lifecycle. This is to safeguard against 149 // leaks, in case of lifecycle manager misbehaving (as it's provided by clients, and 150 // we can't guarantee it's correct). 151 combine(lifecycleManager.displayIds, displayRepository.displayIds) { lifecycleAllowedDisplayIdsnull152 lifecycleAllowedDisplayIds, 153 connectedDisplays -> 154 lifecycleAllowedDisplayIds.intersect(connectedDisplays) 155 } 156 } 157 .stateInTraced( 158 "allowed displays for $debugName", 159 bgApplicationScope, 160 SharingStarted.WhileSubscribed(), 161 setOf(Display.DEFAULT_DISPLAY), 162 ) 163 164 init { 165 bgApplicationScope.launch("$debugName#start") { start() } 166 } 167 startnull168 private suspend fun start() { 169 initCallback.onInit(debugName, this) 170 allowedDisplays.collectLatest { displayIds -> 171 val toRemove = perDisplayInstances.keys - displayIds 172 toRemove.forEach { displayId -> 173 Log.d(TAG, "<$debugName> destroying instance for displayId=$displayId.") 174 perDisplayInstances.remove(displayId)?.let { instance -> 175 (instanceProvider as? PerDisplayInstanceProviderWithTeardown)?.destroyInstance( 176 instance 177 ) 178 } 179 } 180 } 181 } 182 getnull183 override fun get(displayId: Int): T? { 184 if (displayRepository.getDisplay(displayId) == null) { 185 Log.e(TAG, "<$debugName: Display with id $displayId doesn't exist.") 186 return null 187 } 188 189 if (displayId !in allowedDisplays.value) { 190 Log.e( 191 TAG, 192 "<$debugName: Display with id $displayId exists but it's not " + 193 "allowed by lifecycle manager.", 194 ) 195 return null 196 } 197 198 // If it doesn't exist, create it and put it in the map. 199 return perDisplayInstances.computeIfAbsent(displayId) { key -> 200 Log.d(TAG, "<$debugName> creating instance for displayId=$key, as it wasn't available.") 201 val instance = 202 traceSection({ "creating instance of $debugName for displayId=$key" }) { 203 instanceProvider.createInstance(key) 204 } 205 if (instance == null) { 206 Log.e( 207 TAG, 208 "<$debugName> returning null because createInstance($key) returned null.", 209 ) 210 } 211 instance 212 } 213 } 214 215 @AssistedFactory 216 interface Factory<T> { createnull217 fun create( 218 debugName: String, 219 instanceProvider: PerDisplayInstanceProvider<T>, 220 overrideLifecycleManager: DisplayInstanceLifecycleManager? = null, 221 ): PerDisplayInstanceRepositoryImpl<T> 222 } 223 224 companion object { 225 private const val TAG = "PerDisplayInstanceRepo" 226 } 227 toStringnull228 override fun toString(): String { 229 return "PerDisplayInstanceRepositoryImpl(" + 230 "debugName='$debugName', instances=$perDisplayInstances)" 231 } 232 } 233 234 /** 235 * Provides an instance of a given class **only** for the default display, even if asked for another 236 * display. 237 * 238 * This is useful in case of **flag refactors**: it can be provided instead of an instance of 239 * [PerDisplayInstanceRepositoryImpl] when a flag related to multi display refactoring is off. 240 * 241 * Note that this still requires all instances to be provided by a [PerDisplayInstanceProvider]. If 242 * you want to provide an existing instance instead for the default display, either implement it in 243 * a custom [PerDisplayInstanceProvider] (e.g. inject it in the constructor and return it if the 244 * displayId is zero), or use [SingleInstanceRepositoryImpl]. 245 */ 246 class DefaultDisplayOnlyInstanceRepositoryImpl<T>( 247 override val debugName: String, 248 private val instanceProvider: PerDisplayInstanceProvider<T>, 249 ) : PerDisplayRepository<T> { <lambda>null250 private val lazyDefaultDisplayInstance by lazy { 251 instanceProvider.createInstance(Display.DEFAULT_DISPLAY) 252 } 253 getnull254 override fun get(displayId: Int): T? = lazyDefaultDisplayInstance 255 } 256 257 /** 258 * Always returns [instance] for any display. 259 * 260 * This can be used to provide a single instance based on a flag value during a refactor. Similar to 261 * [DefaultDisplayOnlyInstanceRepositoryImpl], but also avoids creating the 262 * [PerDisplayInstanceProvider]. This is useful when you want to provide an existing instance only, 263 * without even instantiating a [PerDisplayInstanceProvider]. 264 */ 265 class SingleInstanceRepositoryImpl<T>(override val debugName: String, private val instance: T) : 266 PerDisplayRepository<T> { 267 override fun get(displayId: Int): T? = instance 268 } 269