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