1 /* 2 * 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 17 package com.android.photopicker.core 18 19 import android.content.Context 20 import android.os.Process 21 import android.os.UserHandle 22 import android.util.Log 23 import com.android.photopicker.core.configuration.ConfigurationManager 24 import com.android.photopicker.core.configuration.DeviceConfigProxyImpl 25 import com.android.photopicker.core.configuration.PhotopickerRuntimeEnv 26 import com.android.photopicker.core.events.Events 27 import com.android.photopicker.core.features.FeatureManager 28 import com.android.photopicker.core.selection.GrantsAwareSelectionImpl 29 import com.android.photopicker.core.selection.Selection 30 import com.android.photopicker.core.selection.SelectionImpl 31 import com.android.photopicker.core.selection.SelectionStrategy 32 import com.android.photopicker.core.selection.SelectionStrategy.Companion.determineSelectionStrategy 33 import com.android.photopicker.core.user.UserMonitor 34 import com.android.photopicker.data.DataService 35 import com.android.photopicker.data.DataServiceImpl 36 import com.android.photopicker.data.MediaProviderClient 37 import com.android.photopicker.data.NotificationService 38 import com.android.photopicker.data.NotificationServiceImpl 39 import com.android.photopicker.data.model.Media 40 import dagger.Module 41 import dagger.Provides 42 import dagger.hilt.InstallIn 43 import dagger.hilt.android.ActivityRetainedLifecycle 44 import dagger.hilt.android.components.ActivityRetainedComponent 45 import dagger.hilt.android.qualifiers.ApplicationContext 46 import dagger.hilt.android.scopes.ActivityRetainedScoped 47 import kotlinx.coroutines.CoroutineDispatcher 48 import kotlinx.coroutines.CoroutineScope 49 import kotlinx.coroutines.SupervisorJob 50 import kotlinx.coroutines.cancel 51 52 /** 53 * Injection Module that provides access to objects bound to the ActivityRetainedScope. 54 * 55 * These can be injected by requesting the type with the [@ActivityRetainedScoped] qualifier. 56 * 57 * The module outlives the individual activities (and survives configuration changes), but is bound 58 * to a single Photopicker session. 59 * 60 * Note: Jobs that are launched in the [CoroutineScope] provided by this module will be 61 * automatically cancelled when the ActivityRetainedScope is ended. 62 */ 63 @Module 64 @InstallIn(ActivityRetainedComponent::class) 65 class ActivityModule { 66 67 companion object { 68 val TAG: String = "PhotopickerActivityModule" 69 } 70 71 // Avoid initialization until it's actually needed. 72 private lateinit var backgroundScope: CoroutineScope 73 private lateinit var configurationManager: ConfigurationManager 74 private lateinit var dataService: DataService 75 private lateinit var events: Events 76 private lateinit var featureManager: FeatureManager 77 private lateinit var mainScope: CoroutineScope 78 private lateinit var notificationService: NotificationService 79 private lateinit var selection: Selection<Media> 80 private lateinit var userMonitor: UserMonitor 81 82 /** Provider for a @Background Dispatcher [CoroutineScope]. */ 83 @Provides 84 @ActivityRetainedScoped 85 @Background provideBackgroundScopenull86 fun provideBackgroundScope( 87 @Background dispatcher: CoroutineDispatcher, 88 activityRetainedLifecycle: ActivityRetainedLifecycle 89 ): CoroutineScope { 90 if (::backgroundScope.isInitialized) { 91 return backgroundScope 92 } else { 93 Log.d(TAG, "Initializing background scope.") 94 backgroundScope = CoroutineScope(SupervisorJob() + dispatcher) 95 activityRetainedLifecycle.addOnClearedListener { 96 Log.d(TAG, "Activity retained lifecycle is ending, cancelling background scope.") 97 backgroundScope.cancel() 98 } 99 return backgroundScope 100 } 101 } 102 103 /** Provider for the [ConfigurationManager]. */ 104 @Provides 105 @ActivityRetainedScoped provideConfigurationManagernull106 fun provideConfigurationManager( 107 @ActivityRetainedScoped @Background scope: CoroutineScope, 108 @Background dispatcher: CoroutineDispatcher, 109 ): ConfigurationManager { 110 if (::configurationManager.isInitialized) { 111 return configurationManager 112 } else { 113 Log.d( 114 ConfigurationManager.TAG, 115 "ConfigurationManager requested but not yet initialized." + 116 " Initializing ConfigurationManager." 117 ) 118 configurationManager = 119 ConfigurationManager( 120 /* runtimeEnv= */ PhotopickerRuntimeEnv.ACTIVITY, 121 /* scope= */ scope, 122 /* dispatcher= */ dispatcher, 123 /* deviceConfigProxy= */ DeviceConfigProxyImpl(), 124 ) 125 return configurationManager 126 } 127 } 128 129 /** 130 * Provider method for [DataService]. This is lazily initialized only when requested to save on 131 * initialization costs of this module. 132 * 133 * Ideally this should not be limited to an Activity scope. This is planned to be changed soon. 134 */ 135 @Provides 136 @ActivityRetainedScoped provideDataServicenull137 fun provideDataService( 138 @ActivityRetainedScoped @Background scope: CoroutineScope, 139 @Background dispatcher: CoroutineDispatcher, 140 @ActivityRetainedScoped userMonitor: UserMonitor, 141 @ActivityRetainedScoped notificationService: NotificationService, 142 @ActivityRetainedScoped configurationManager: ConfigurationManager, 143 @ActivityRetainedScoped featureManager: FeatureManager 144 ): DataService { 145 if (!::dataService.isInitialized) { 146 Log.d( 147 DataService.TAG, 148 "DataService requested but not yet initialized. Initializing DataService." 149 ) 150 dataService = 151 DataServiceImpl( 152 userMonitor.userStatus, 153 scope, 154 dispatcher, 155 notificationService, 156 MediaProviderClient(), 157 configurationManager.configuration, 158 featureManager 159 ) 160 } 161 return dataService 162 } 163 164 /** 165 * Provider method for [Events]. This is lazily initialized only when requested to save on 166 * initialization costs of this module. 167 */ 168 @Provides 169 @ActivityRetainedScoped provideEventsnull170 fun provideEvents( 171 @Background scope: CoroutineScope, 172 featureManager: FeatureManager, 173 configurationManager: ConfigurationManager, 174 ): Events { 175 if (::events.isInitialized) { 176 return events 177 } else { 178 Log.d(Events.TAG, "Events requested but not yet initialized. Initializing Events.") 179 return Events(scope, configurationManager.configuration, featureManager) 180 } 181 } 182 183 @Provides 184 @ActivityRetainedScoped provideFeatureManagernull185 fun provideFeatureManager( 186 @ActivityRetainedScoped @Background scope: CoroutineScope, 187 @ActivityRetainedScoped configurationManager: ConfigurationManager, 188 ): FeatureManager { 189 190 if (::featureManager.isInitialized) { 191 return featureManager 192 } else { 193 Log.d( 194 FeatureManager.TAG, 195 "FeatureManager requested but not yet initialized. Initializing FeatureManager." 196 ) 197 featureManager = 198 // Do not pass a set of FeatureRegistrations here to use the standard set of 199 // enabled features. 200 FeatureManager( 201 configurationManager.configuration, 202 scope, 203 ) 204 return featureManager 205 } 206 } 207 208 /** Provider for a @Main Dispatcher [CoroutineScope]. */ 209 @Provides 210 @ActivityRetainedScoped 211 @Main provideMainScopenull212 fun provideMainScope( 213 @Main dispatcher: CoroutineDispatcher, 214 activityRetainedLifecycle: ActivityRetainedLifecycle 215 ): CoroutineScope { 216 217 if (::mainScope.isInitialized) { 218 return mainScope 219 } else { 220 Log.d(TAG, "Initializing main scope.") 221 mainScope = CoroutineScope(SupervisorJob() + dispatcher) 222 activityRetainedLifecycle.addOnClearedListener { 223 Log.d(TAG, "Activity retained lifecycle is ending, cancelling main scope.") 224 mainScope.cancel() 225 } 226 return mainScope 227 } 228 } 229 230 @Provides 231 @ActivityRetainedScoped provideNotificationServicenull232 fun provideNotificationService(): NotificationService { 233 234 if (!::notificationService.isInitialized) { 235 Log.d( 236 NotificationService.TAG, 237 "NotificationService requested but not yet initialized. " + 238 "Initializing NotificationService." 239 ) 240 notificationService = NotificationServiceImpl() 241 } 242 return notificationService 243 } 244 245 @Provides 246 @ActivityRetainedScoped provideSelectionnull247 fun provideSelection( 248 @ActivityRetainedScoped @Background scope: CoroutineScope, 249 configurationManager: ConfigurationManager, 250 ): Selection<Media> { 251 252 if (::selection.isInitialized) { 253 return selection 254 } else { 255 Log.d(TAG, "Initializing selection.") 256 selection = 257 when (determineSelectionStrategy(configurationManager.configuration.value)) { 258 SelectionStrategy.GRANTS_AWARE_SELECTION -> 259 GrantsAwareSelectionImpl( 260 scope = scope, 261 configuration = configurationManager.configuration, 262 ) 263 264 SelectionStrategy.DEFAULT -> 265 SelectionImpl( 266 scope = scope, 267 configuration = configurationManager.configuration, 268 ) 269 } 270 return selection 271 } 272 } 273 274 /** Provides the UserHandle of the current process owner. */ 275 @Provides 276 @ActivityRetainedScoped provideUserHandlenull277 fun provideUserHandle(): UserHandle { 278 return Process.myUserHandle() 279 } 280 281 /** Provider for the [UserMonitor]. This is lazily initialized only when requested. */ 282 @Provides 283 @ActivityRetainedScoped provideUserMonitornull284 fun provideUserMonitor( 285 @ApplicationContext context: Context, 286 @ActivityRetainedScoped configurationManager: ConfigurationManager, 287 @ActivityRetainedScoped @Background scope: CoroutineScope, 288 @Background dispatcher: CoroutineDispatcher, 289 @ActivityRetainedScoped handle: UserHandle, 290 ): UserMonitor { 291 if (::userMonitor.isInitialized) { 292 return userMonitor 293 } else { 294 Log.d( 295 UserMonitor.TAG, 296 "UserMonitor requested but not yet initialized. Initializing UserMonitor." 297 ) 298 userMonitor = 299 UserMonitor(context, configurationManager.configuration, scope, dispatcher, handle) 300 return userMonitor 301 } 302 } 303 } 304