• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.quickstep.recents.di
18 
19 import android.content.Context
20 import android.util.Log
21 import com.android.launcher3.util.coroutines.DispatcherProvider
22 import com.android.launcher3.util.coroutines.ProductionDispatchers
23 import com.android.quickstep.RecentsModel
24 import com.android.quickstep.recents.data.RecentTasksRepository
25 import com.android.quickstep.recents.data.TaskVisualsChangedDelegate
26 import com.android.quickstep.recents.data.TaskVisualsChangedDelegateImpl
27 import com.android.quickstep.recents.data.TasksRepository
28 import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
29 import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
30 import com.android.quickstep.recents.domain.usecase.GetThumbnailPositionUseCase
31 import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase
32 import com.android.quickstep.recents.domain.usecase.OrganizeDesktopTasksUseCase
33 import com.android.quickstep.recents.viewmodel.RecentsViewData
34 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper.PreviewPositionHelperFactory
35 import kotlinx.coroutines.CoroutineName
36 import kotlinx.coroutines.CoroutineScope
37 import kotlinx.coroutines.SupervisorJob
38 
39 internal typealias RecentsScopeId = String
40 
41 class RecentsDependencies private constructor(appContext: Context) {
42     private val scopes = mutableMapOf<RecentsScopeId, RecentsDependenciesScope>()
43 
44     init {
45         startDefaultScope(appContext)
46     }
47 
48     /**
49      * This function initialises the default scope with RecentsView dependencies. Some dependencies
50      * are global while others are per-RecentsView. The scope is used to differentiate between
51      * RecentsViews.
52      */
53     private fun startDefaultScope(appContext: Context) {
54         Log.d(TAG, "startDefaultScope")
55         createScope(DEFAULT_SCOPE_ID).apply {
56             set(RecentsViewData::class.java.simpleName, RecentsViewData())
57             val dispatcherProvider: DispatcherProvider = ProductionDispatchers
58             val recentsCoroutineScope =
59                 CoroutineScope(
60                     SupervisorJob() + dispatcherProvider.unconfined + CoroutineName("RecentsView")
61                 )
62             set(CoroutineScope::class.java.simpleName, recentsCoroutineScope)
63             set(DispatcherProvider::class.java.simpleName, dispatcherProvider)
64             val recentsModel = RecentsModel.INSTANCE.get(appContext)
65             val taskVisualsChangedDelegate =
66                 TaskVisualsChangedDelegateImpl(
67                     recentsModel,
68                     recentsModel.thumbnailCache.highResLoadingState,
69                 )
70             set(TaskVisualsChangedDelegate::class.java.simpleName, taskVisualsChangedDelegate)
71 
72             // Create RecentsTaskRepository singleton
73             val recentTasksRepository: RecentTasksRepository =
74                 with(recentsModel) {
75                     TasksRepository(
76                         this,
77                         thumbnailCache,
78                         iconCache,
79                         taskVisualsChangedDelegate,
80                         recentsCoroutineScope,
81                         ProductionDispatchers,
82                     )
83                 }
84             set(RecentTasksRepository::class.java.simpleName, recentTasksRepository)
85         }
86     }
87 
88     /**
89      * This function initialises a scope associated with the dependencies of a single RecentsView.
90      *
91      * @param viewContext the Context associated with a RecentsView.
92      * @return the scope id associated with the new RecentsDependenciesScope.
93      */
94     fun createRecentsViewScope(viewContext: Context): String {
95         val scopeId = viewContext.hashCode().toString()
96         Log.d(TAG, "createRecentsViewScope $scopeId")
97         val scope =
98             createScope(scopeId).apply {
99                 set(RecentsViewData::class.java.simpleName, RecentsViewData())
100                 val dispatcherProvider: DispatcherProvider =
101                     get<DispatcherProvider>(DEFAULT_SCOPE_ID)
102                 val recentsCoroutineScope =
103                     CoroutineScope(
104                         SupervisorJob() +
105                             dispatcherProvider.unconfined +
106                             CoroutineName("RecentsView$scopeId")
107                     )
108                 set(CoroutineScope::class.java.simpleName, recentsCoroutineScope)
109             }
110         scope.linkTo(getScope(DEFAULT_SCOPE_ID))
111         return scopeId
112     }
113 
114     inline fun <reified T> inject(
115         scopeId: RecentsScopeId = "",
116         extras: RecentsDependenciesExtras = RecentsDependenciesExtras(),
117         noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
118     ): T = inject(T::class.java, scopeId = scopeId, extras = extras, factory = factory)
119 
120     @Suppress("UNCHECKED_CAST")
121     @JvmOverloads
122     fun <T> inject(
123         modelClass: Class<T>,
124         scopeId: RecentsScopeId = DEFAULT_SCOPE_ID,
125         extras: RecentsDependenciesExtras = RecentsDependenciesExtras(),
126         factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
127     ): T {
128         val currentScopeId = scopeId.ifEmpty { DEFAULT_SCOPE_ID }
129         val scope = scopes[currentScopeId] ?: createScope(currentScopeId)
130 
131         log("inject ${modelClass.simpleName} into ${scope.scopeId}", Log.INFO)
132         var instance: T?
133         synchronized(this) {
134             instance = getDependency(scope, modelClass)
135             log("found instance? $instance", Log.INFO)
136             if (instance == null) {
137                 instance =
138                     factory?.invoke(extras) as T ?: createDependency(modelClass, scopeId, extras)
139                 scope[modelClass.simpleName] = instance!!
140                 log(
141                     "instance of $modelClass" +
142                         " (${instance.hashCode()}) added to scope ${scope.scopeId}"
143                 )
144             }
145         }
146         return instance!!
147     }
148 
149     inline fun <reified T> provide(scopeId: RecentsScopeId = "", noinline factory: () -> T): T =
150         provide(T::class.java, scopeId = scopeId, factory = factory)
151 
152     @JvmOverloads
153     fun <T> provide(
154         modelClass: Class<T>,
155         scopeId: RecentsScopeId = DEFAULT_SCOPE_ID,
156         factory: () -> T,
157     ) = inject(modelClass, scopeId, factory = { factory.invoke() })
158 
159     private fun <T> getDependency(scope: RecentsDependenciesScope, modelClass: Class<T>): T? {
160         var instance: T? = scope[modelClass.simpleName] as T?
161         if (instance == null) {
162             instance =
163                 scope.scopeIdsLinked.firstNotNullOfOrNull { scopeId ->
164                     getScope(scopeId)[modelClass.simpleName]
165                 } as T?
166         }
167         if (instance != null) log("Found dependency: $instance", Log.INFO)
168         return instance
169     }
170 
171     fun getScope(scope: Any): RecentsDependenciesScope {
172         val scopeId: RecentsScopeId = scope as? RecentsScopeId ?: scope.hashCode().toString()
173         return getScope(scopeId)
174     }
175 
176     fun getScope(scopeId: RecentsScopeId): RecentsDependenciesScope =
177         scopes[scopeId] ?: createScope(scopeId)
178 
179     fun removeScope(scope: Any) {
180         val scopeId: RecentsScopeId = scope as? RecentsScopeId ?: scope.hashCode().toString()
181         scopes[scopeId]?.close()
182         scopes.remove(scopeId)
183         log("Scope $scopeId removed")
184     }
185 
186     // TODO(b/353912757): Create a factory so we can prevent this method of growing indefinitely.
187     //  Each class should be responsible for providing a factory function to create a new instance.
188     @Suppress("UNCHECKED_CAST")
189     private fun <T> createDependency(
190         modelClass: Class<T>,
191         scopeId: RecentsScopeId,
192         extras: RecentsDependenciesExtras,
193     ): T {
194         log("createDependency ${modelClass.simpleName} with $scopeId and $extras started", Log.WARN)
195         log("linked scopes: ${getScope(scopeId).scopeIdsLinked}")
196         val instance: Any =
197             when (modelClass) {
198                 IsThumbnailValidUseCase::class.java ->
199                     IsThumbnailValidUseCase(rotationStateRepository = inject(scopeId))
200                 GetTaskUseCase::class.java -> GetTaskUseCase(repository = inject(scopeId))
201                 GetSysUiStatusNavFlagsUseCase::class.java -> GetSysUiStatusNavFlagsUseCase()
202                 GetThumbnailPositionUseCase::class.java ->
203                     GetThumbnailPositionUseCase(
204                         deviceProfileRepository = inject(scopeId),
205                         rotationStateRepository = inject(scopeId),
206                         previewPositionHelperFactory = PreviewPositionHelperFactory(),
207                     )
208                 OrganizeDesktopTasksUseCase::class.java -> OrganizeDesktopTasksUseCase()
209                 else -> {
210                     log("Factory for ${modelClass.simpleName} not defined!", Log.ERROR)
211                     error("Factory for ${modelClass.simpleName} not defined!")
212                 }
213             }
214         return (instance as T).also {
215             log(
216                 "createDependency ${modelClass.simpleName} with $scopeId and $extras completed",
217                 Log.WARN,
218             )
219         }
220     }
221 
222     private fun createScope(scopeId: RecentsScopeId): RecentsDependenciesScope {
223         return RecentsDependenciesScope(scopeId).also { scopes[scopeId] = it }
224     }
225 
226     private fun log(message: String, @Log.Level level: Int = Log.DEBUG) {
227         if (DEBUG) {
228             when (level) {
229                 Log.WARN -> Log.w(TAG, message)
230                 Log.VERBOSE -> Log.v(TAG, message)
231                 Log.INFO -> Log.i(TAG, message)
232                 Log.ERROR -> Log.e(TAG, message)
233                 else -> Log.d(TAG, message)
234             }
235         }
236     }
237 
238     companion object {
239         const val DEFAULT_SCOPE_ID = "RecentsDependencies::GlobalScope"
240         private const val TAG = "RecentsDependencies"
241         private const val DEBUG = false
242 
243         @Volatile private var instance: RecentsDependencies? = null
244 
245         private fun initialize(context: Context): RecentsDependencies {
246             Log.d(TAG, "initializing")
247             synchronized(this) {
248                 val newInstance = RecentsDependencies(context.applicationContext)
249                 instance = newInstance
250                 return newInstance
251             }
252         }
253 
254         fun maybeInitialize(context: Context): RecentsDependencies {
255             return instance ?: initialize(context)
256         }
257 
258         fun getInstance(): RecentsDependencies {
259             return instance
260                 ?: throw UninitializedPropertyAccessException(
261                     "Recents dependencies are not initialized. " +
262                         "Call `RecentsDependencies.maybeInitialize` before using this container."
263                 )
264         }
265 
266         @JvmStatic
267         fun destroy(viewContext: Context) {
268             synchronized(this) {
269                 val localInstance = instance ?: return
270                 val scopeId = viewContext.hashCode().toString()
271                 val scope = localInstance.scopes[scopeId]
272                 if (scope == null) {
273                     Log.e(
274                         TAG,
275                         "Trying to destroy an unknown scope. Scopes: ${localInstance.scopes.size}",
276                     )
277                     return
278                 }
279                 scope.close()
280                 localInstance.scopes.remove(scopeId)
281                 if (DEBUG) {
282                     Log.d(TAG, "destroyed $scopeId", Exception("Printing stack trace"))
283                 } else {
284                     Log.d(TAG, "destroyed $scopeId")
285                 }
286                 if (localInstance.scopes.size == 1) {
287                     // Only the default scope left - destroy this too.
288                     instance = null
289                     Log.d(TAG, "also destroyed default scope")
290                 }
291             }
292         }
293     }
294 }
295 
injectnull296 inline fun <reified T> RecentsDependencies.Companion.inject(
297     scope: Any = "",
298     vararg extras: Pair<String, Any>,
299     noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
300 ): Lazy<T> = lazy { get(scope, RecentsDependenciesExtras(extras), factory) }
301 
getnull302 inline fun <reified T> RecentsDependencies.Companion.get(
303     scope: Any = "",
304     extras: RecentsDependenciesExtras = RecentsDependenciesExtras(),
305     noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
306 ): T {
307     val scopeId: RecentsScopeId = scope as? RecentsScopeId ?: scope.hashCode().toString()
308     return getInstance().inject(scopeId, extras, factory)
309 }
310 
getnull311 inline fun <reified T> RecentsDependencies.Companion.get(
312     scope: Any = "",
313     vararg extras: Pair<String, Any>,
314     noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
315 ): T = get(scope, RecentsDependenciesExtras(extras), factory)
316 
317 fun RecentsDependencies.Companion.getScope(scopeId: Any) = getInstance().getScope(scopeId)
318