• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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