• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 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 package com.android.photopicker.core
17 
18 import android.content.Context
19 import android.os.Process
20 import android.os.UserHandle
21 import android.util.Log
22 import androidx.lifecycle.Lifecycle
23 import androidx.lifecycle.LifecycleEventObserver
24 import com.android.photopicker.core.banners.BannerManager
25 import com.android.photopicker.core.banners.BannerManagerImpl
26 import com.android.photopicker.core.configuration.ConfigurationManager
27 import com.android.photopicker.core.configuration.DeviceConfigProxy
28 import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv
29 import com.android.photopicker.core.database.DatabaseManager
30 import com.android.photopicker.core.database.DatabaseManagerImpl
31 import com.android.photopicker.core.embedded.EmbeddedLifecycle
32 import com.android.photopicker.core.embedded.EmbeddedViewModelFactory
33 import com.android.photopicker.core.events.Events
34 import com.android.photopicker.core.events.generatePickerSessionId
35 import com.android.photopicker.core.features.FeatureManager
36 import com.android.photopicker.core.selection.GrantsAwareSelectionImpl
37 import com.android.photopicker.core.selection.Selection
38 import com.android.photopicker.core.selection.SelectionImpl
39 import com.android.photopicker.core.selection.SelectionStrategy
40 import com.android.photopicker.core.selection.SelectionStrategy.Companion.determineSelectionStrategy
41 import com.android.photopicker.core.user.UserMonitor
42 import com.android.photopicker.data.DataService
43 import com.android.photopicker.data.DataServiceImpl
44 import com.android.photopicker.data.MediaProviderClient
45 import com.android.photopicker.data.NotificationService
46 import com.android.photopicker.data.NotificationServiceImpl
47 import com.android.photopicker.data.PrefetchDataService
48 import com.android.photopicker.data.PrefetchDataServiceImpl
49 import com.android.photopicker.data.model.Media
50 import com.android.photopicker.features.categorygrid.data.CategoryDataService
51 import com.android.photopicker.features.search.data.SearchDataService
52 import dagger.Lazy
53 import dagger.Module
54 import dagger.Provides
55 import dagger.hilt.InstallIn
56 import dagger.hilt.android.qualifiers.ApplicationContext
57 import kotlinx.coroutines.CoroutineDispatcher
58 import kotlinx.coroutines.CoroutineScope
59 import kotlinx.coroutines.SupervisorJob
60 import kotlinx.coroutines.cancel
61 import kotlinx.coroutines.runBlocking
62 
63 /**
64  * Injection Module that provides access to objects bound to a single [EmbeddedServiceComponent].
65  *
66  * The module is bound to a single instance of the embedded Photopicker, and first obtained in the
67  * [Session].
68  *
69  * Note: Jobs that are launched in the [CoroutineScope] provided by this module will be
70  * automatically cancelled when the [EmbeddedLifecycle] provided by this module ends.
71  */
72 @Module
73 @InstallIn(EmbeddedServiceComponent::class)
74 class EmbeddedServiceModule {
75 
76     companion object {
77         val TAG: String = "PhotopickerEmbeddedModule"
78     }
79 
80     // Avoid initialization until it's actually needed.
81     private lateinit var backgroundScope: CoroutineScope
82     private lateinit var bannerManager: BannerManager
83     private lateinit var configurationManager: ConfigurationManager
84     private lateinit var databaseManager: DatabaseManager
85     private lateinit var dataService: DataService
86     private lateinit var events: Events
87     private lateinit var embeddedLifecycle: EmbeddedLifecycle
88     private lateinit var embeddedViewModelFactory: EmbeddedViewModelFactory
89     private lateinit var featureManager: FeatureManager
90     private lateinit var mainScope: CoroutineScope
91     private lateinit var notificationService: NotificationService
92     private lateinit var prefetchDataService: PrefetchDataService
93     private lateinit var selection: Selection<Media>
94     private lateinit var userMonitor: UserMonitor
95 
96     @Provides
97     @SessionScoped
98     fun provideEmbeddedLifecycle(
99         viewModelFactory: EmbeddedViewModelFactory,
100         @Main dispatcher: CoroutineDispatcher,
101     ): EmbeddedLifecycle {
102         if (::embeddedLifecycle.isInitialized) {
103             return embeddedLifecycle
104         } else {
105             Log.d(TAG, "Initializing custom embedded lifecycle.")
106 
107             // This must initialize on the MainThread so the Lifecycle state can be set.
108             embeddedLifecycle = runBlocking(dispatcher) { EmbeddedLifecycle(viewModelFactory) }
109             return embeddedLifecycle
110         }
111     }
112 
113     @Provides
114     @SessionScoped
115     fun provideViewModelFactory(
116         @Background backgroundDispatcher: CoroutineDispatcher,
117         featureManager: Lazy<FeatureManager>,
118         configurationManager: Lazy<ConfigurationManager>,
119         bannerManager: Lazy<BannerManager>,
120         selection: Lazy<Selection<Media>>,
121         userMonitor: Lazy<UserMonitor>,
122         dataService: Lazy<DataService>,
123         searchDataService: Lazy<SearchDataService>,
124         categoryDataService: Lazy<CategoryDataService>,
125         events: Lazy<Events>,
126     ): EmbeddedViewModelFactory {
127         if (::embeddedViewModelFactory.isInitialized) {
128             return embeddedViewModelFactory
129         } else {
130             Log.d(TAG, "Initializing embedded view model factory.")
131             embeddedViewModelFactory =
132                 EmbeddedViewModelFactory(
133                     backgroundDispatcher,
134                     configurationManager,
135                     bannerManager,
136                     dataService,
137                     searchDataService,
138                     categoryDataService,
139                     events,
140                     featureManager,
141                     selection,
142                     userMonitor,
143                 )
144             return embeddedViewModelFactory
145         }
146     }
147 
148     /** Provider for a @Background Dispatcher [CoroutineScope]. */
149     @Provides
150     @SessionScoped
151     @Background
152     fun provideBackgroundScope(
153         @Background dispatcher: CoroutineDispatcher,
154         @Main mainDispatcher: CoroutineDispatcher,
155         embeddedLifecycle: EmbeddedLifecycle,
156     ): CoroutineScope {
157         if (::backgroundScope.isInitialized) {
158             return backgroundScope
159         } else {
160             Log.d(TAG, "Initializing background scope.")
161             backgroundScope = CoroutineScope(SupervisorJob() + dispatcher)
162 
163             // addObserver must be called from the main thread
164             runBlocking(mainDispatcher) {
165                 embeddedLifecycle.lifecycle.addObserver(
166                     LifecycleEventObserver { _, event ->
167                         when (event) {
168                             Lifecycle.Event.ON_DESTROY -> {
169                                 Log.d(
170                                     TAG,
171                                     "Embedded lifecycle is ending, cancelling background scope.",
172                                 )
173                                 backgroundScope.cancel()
174                             }
175                             else -> {}
176                         }
177                     }
178                 )
179             }
180             return backgroundScope
181         }
182     }
183 
184     /** Provider for an implementation of [BannerManager]. */
185     @Provides
186     @SessionScoped
187     fun provideBannerManager(
188         @Background backgroundScope: CoroutineScope,
189         @Background backgroundDispatcher: CoroutineDispatcher,
190         configurationManager: ConfigurationManager,
191         databaseManager: DatabaseManager,
192         featureManager: FeatureManager,
193         dataService: DataService,
194         userMonitor: UserMonitor,
195         processOwnerHandle: UserHandle,
196     ): BannerManager {
197         if (::bannerManager.isInitialized) {
198             return bannerManager
199         } else {
200             Log.d(TAG, "BannerManager requested and initializing.")
201             bannerManager =
202                 BannerManagerImpl(
203                     backgroundScope,
204                     backgroundDispatcher,
205                     configurationManager,
206                     databaseManager,
207                     featureManager,
208                     dataService,
209                     userMonitor,
210                     processOwnerHandle,
211                 )
212             return bannerManager
213         }
214     }
215 
216     /** Provider for the [ConfigurationManager]. */
217     @Provides
218     @SessionScoped
219     fun provideConfigurationManager(
220         @Background scope: CoroutineScope,
221         @Background dispatcher: CoroutineDispatcher,
222         deviceConfigProxy: DeviceConfigProxy,
223     ): ConfigurationManager {
224         if (::configurationManager.isInitialized) {
225             return configurationManager
226         } else {
227             Log.d(
228                 ConfigurationManager.TAG,
229                 "ConfigurationManager requested but not yet initialized." +
230                     " Initializing ConfigurationManager.",
231             )
232             configurationManager =
233                 ConfigurationManager(
234                     /* runtimeEnv= */ PhotopickerRuntimeEnv.EMBEDDED,
235                     /* scope= */ scope,
236                     /* dispatcher= */ dispatcher,
237                     /* deviceConfigProxy= */ deviceConfigProxy,
238                     /* sessionId */ generatePickerSessionId(),
239                 )
240             return configurationManager
241         }
242     }
243 
244     /**
245      * Provider method for [DataService]. This is lazily initialized only when requested to save on
246      * initialization costs of this module.
247      */
248     @Provides
249     @SessionScoped
250     fun provideDataService(
251         @Background scope: CoroutineScope,
252         @Background dispatcher: CoroutineDispatcher,
253         userMonitor: UserMonitor,
254         notificationService: NotificationService,
255         configurationManager: ConfigurationManager,
256         featureManager: FeatureManager,
257         @ApplicationContext appContext: Context,
258         events: Events,
259         processOwnerHandle: UserHandle,
260         mediaProviderClient: MediaProviderClient,
261     ): DataService {
262 
263         if (!::dataService.isInitialized) {
264             Log.d(
265                 DataService.TAG,
266                 "DataService requested but not yet initialized. Initializing DataService.",
267             )
268             dataService =
269                 DataServiceImpl(
270                     userMonitor.userStatus,
271                     scope,
272                     dispatcher,
273                     notificationService,
274                     mediaProviderClient,
275                     configurationManager.configuration,
276                     featureManager,
277                     appContext,
278                     events,
279                     processOwnerHandle,
280                 )
281         }
282         return dataService
283     }
284 
285     @Provides
286     @SessionScoped
287     fun provideDatabaseManager(@ApplicationContext context: Context): DatabaseManager {
288         if (::databaseManager.isInitialized) {
289             return databaseManager
290         } else {
291             Log.d(TAG, "Initializing DatabaseManager")
292             databaseManager = DatabaseManagerImpl(context)
293             return databaseManager
294         }
295     }
296 
297     /**
298      * Provider method for [Events]. This is lazily initialized only when requested to save on
299      * initialization costs of this module.
300      */
301     @Provides
302     @SessionScoped
303     fun provideEvents(
304         @Background scope: CoroutineScope,
305         featureManager: FeatureManager,
306         configurationManager: ConfigurationManager,
307     ): Events {
308         if (::events.isInitialized) {
309             return events
310         } else {
311             Log.d(Events.TAG, "Events requested but not yet initialized. Initializing Events.")
312             events = Events(scope, configurationManager.configuration, featureManager)
313             return events
314         }
315     }
316 
317     @Provides
318     @SessionScoped
319     fun provideFeatureManager(
320         @SessionScoped @Background scope: CoroutineScope,
321         @SessionScoped configurationManager: ConfigurationManager,
322         prefetchDataService: PrefetchDataService,
323         @Background backgroundDispatcher: CoroutineDispatcher,
324     ): FeatureManager {
325 
326         if (::featureManager.isInitialized) {
327             return featureManager
328         } else {
329             Log.d(
330                 FeatureManager.TAG,
331                 "FeatureManager requested but not yet initialized. Initializing FeatureManager.",
332             )
333             featureManager =
334                 // Do not pass a set of FeatureRegistrations here to use the standard set of
335                 // enabled features.
336                 FeatureManager(
337                     configuration = configurationManager.configuration,
338                     scope = scope,
339                     prefetchDataService = prefetchDataService,
340                     dispatcher = backgroundDispatcher,
341                 )
342             return featureManager
343         }
344     }
345 
346     /** Provider for a @Main Dispatcher [CoroutineScope]. */
347     @Provides
348     @SessionScoped
349     @Main
350     fun provideMainScope(
351         @Main dispatcher: CoroutineDispatcher,
352         embeddedLifecycle: EmbeddedLifecycle,
353     ): CoroutineScope {
354 
355         if (::mainScope.isInitialized) {
356             return mainScope
357         } else {
358             Log.d(TAG, "Initializing main scope.")
359             mainScope = CoroutineScope(SupervisorJob() + dispatcher)
360 
361             // addObserver must be called from the main thread
362             runBlocking(dispatcher) {
363                 embeddedLifecycle.lifecycle.addObserver(
364                     LifecycleEventObserver { _, event ->
365                         when (event) {
366                             Lifecycle.Event.ON_DESTROY -> {
367                                 Log.d(TAG, "Embedded lifecycle is ending, cancelling main scope.")
368                                 mainScope.cancel()
369                             }
370                             else -> {}
371                         }
372                     }
373                 )
374             }
375             return mainScope
376         }
377     }
378 
379     @Provides
380     @SessionScoped
381     fun provideNotificationService(): NotificationService {
382 
383         if (!::notificationService.isInitialized) {
384             Log.d(
385                 NotificationService.TAG,
386                 "NotificationService requested but not yet initialized. " +
387                     "Initializing NotificationService.",
388             )
389             notificationService = NotificationServiceImpl()
390         }
391         return notificationService
392     }
393 
394     @Provides
395     @SessionScoped
396     fun providePrefetchDataService(
397         userMonitor: UserMonitor,
398         @ApplicationContext context: Context,
399         @Background backgroundDispatcher: CoroutineDispatcher,
400         mediaProviderClient: MediaProviderClient,
401         @Background scope: CoroutineScope,
402     ): PrefetchDataService {
403 
404         if (!::prefetchDataService.isInitialized) {
405             Log.d(
406                 PrefetchDataService.TAG,
407                 "PrefetchDataService requested but not yet initialized. " +
408                     "Initializing PrefetchDataService.",
409             )
410             prefetchDataService =
411                 PrefetchDataServiceImpl(
412                     mediaProviderClient,
413                     userMonitor,
414                     context,
415                     backgroundDispatcher,
416                     scope,
417                 )
418         }
419         return prefetchDataService
420     }
421 
422     @Provides
423     @SessionScoped
424     fun provideSelection(
425         @Background scope: CoroutineScope,
426         configurationManager: ConfigurationManager,
427         dataService: DataService,
428     ): Selection<Media> {
429 
430         if (::selection.isInitialized) {
431             return selection
432         } else {
433             Log.d(TAG, "Initializing selection.")
434             selection =
435                 when (determineSelectionStrategy(configurationManager.configuration.value)) {
436                     SelectionStrategy.GRANTS_AWARE_SELECTION ->
437                         GrantsAwareSelectionImpl(
438                             scope = scope,
439                             configuration = configurationManager.configuration,
440                             preGrantedItemsCount = dataService.preGrantedMediaCount,
441                         )
442                     SelectionStrategy.DEFAULT ->
443                         SelectionImpl(
444                             scope = scope,
445                             configuration = configurationManager.configuration,
446                             preSelectedMedia = dataService.preSelectionMediaData,
447                         )
448                 }
449             return selection
450         }
451     }
452 
453     /** Provides the UserHandle of the current process owner. */
454     @Provides
455     @SessionScoped
456     fun provideUserHandle(): UserHandle {
457         return Process.myUserHandle()
458     }
459 
460     /** Provider for the [UserMonitor]. This is lazily initialized only when requested. */
461     @Provides
462     @SessionScoped
463     fun provideUserMonitor(
464         @ApplicationContext context: Context,
465         configurationManager: ConfigurationManager,
466         @Background scope: CoroutineScope,
467         @Background dispatcher: CoroutineDispatcher,
468         handle: UserHandle,
469     ): UserMonitor {
470         if (::userMonitor.isInitialized) {
471             return userMonitor
472         } else {
473             Log.d(
474                 UserMonitor.TAG,
475                 "UserMonitor requested but not yet initialized. Initializing UserMonitor.",
476             )
477             userMonitor =
478                 UserMonitor(context, configurationManager.configuration, scope, dispatcher, handle)
479             return userMonitor
480         }
481     }
482 }
483