1 /* 2 * 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.photopicker.inject 18 19 import android.content.Context 20 import android.os.UserHandle 21 import com.android.photopicker.core.Background 22 import com.android.photopicker.core.Main 23 import com.android.photopicker.core.banners.BannerManager 24 import com.android.photopicker.core.banners.BannerManagerImpl 25 import com.android.photopicker.core.configuration.ConfigurationManager 26 import com.android.photopicker.core.configuration.DeviceConfigProxy 27 import com.android.photopicker.core.configuration.TestDeviceConfigProxyImpl 28 import com.android.photopicker.core.database.DatabaseManager 29 import com.android.photopicker.core.database.DatabaseManagerTestImpl 30 import com.android.photopicker.core.embedded.EmbeddedLifecycle 31 import com.android.photopicker.core.embedded.EmbeddedViewModelFactory 32 import com.android.photopicker.core.events.Events 33 import com.android.photopicker.core.events.generatePickerSessionId 34 import com.android.photopicker.core.features.FeatureManager 35 import com.android.photopicker.core.selection.GrantsAwareSelectionImpl 36 import com.android.photopicker.core.selection.Selection 37 import com.android.photopicker.core.selection.SelectionImpl 38 import com.android.photopicker.core.selection.SelectionStrategy 39 import com.android.photopicker.core.selection.SelectionStrategy.Companion.determineSelectionStrategy 40 import com.android.photopicker.core.user.UserMonitor 41 import com.android.photopicker.data.DataService 42 import com.android.photopicker.data.MediaProviderClient 43 import com.android.photopicker.data.PrefetchDataService 44 import com.android.photopicker.data.TestDataServiceImpl 45 import com.android.photopicker.data.TestPrefetchDataService 46 import com.android.photopicker.data.model.Media 47 import com.android.photopicker.features.categorygrid.data.CategoryDataService 48 import com.android.photopicker.features.search.data.SearchDataService 49 import dagger.Lazy 50 import dagger.Module 51 import dagger.Provides 52 import dagger.hilt.migration.DisableInstallInCheck 53 import javax.inject.Singleton 54 import kotlinx.coroutines.CoroutineDispatcher 55 import kotlinx.coroutines.CoroutineScope 56 import kotlinx.coroutines.Dispatchers 57 import kotlinx.coroutines.runBlocking 58 import org.mockito.Mockito.mock 59 60 /** 61 * A basic Hilt test module that resolves common injected dependencies. Tests can install extend and 62 * install this module to customize dependencies on a per test suite basis and avoid a lot of DI 63 * setup of classes that may not be used in the test cases. 64 * 65 * For the purposes of the test case, all of these values are provided as [@Singleton] to ensure 66 * that each injector receives the same value. 67 * 68 * This module does not provide overrides for CoroutineScope bindings, as those should be provided 69 * by the test class directly via [@BindValue] and [TestScope] 70 * 71 * Note: This module is not actually Installed into the Hilt graph. @DisableInstallInCheck prevents 72 * this module from being installed, each test will extend & install this module independently. 73 */ 74 @Module 75 @DisableInstallInCheck 76 abstract class PhotopickerTestModule(val options: TestOptions = TestOptions.Builder().build()) { 77 78 @Singleton 79 @Provides provideTestMockContextnull80 fun provideTestMockContext(): Context { 81 // Always provide a Mocked Context object to injected dependencies, to ensure that the 82 // device state never leaks into the test environment. Feature tests can obtain this mock 83 // by injecting a context object. 84 return mock(Context::class.java) 85 } 86 87 @Singleton 88 @Provides provideEmbeddedLifecyclenull89 fun provideEmbeddedLifecycle( 90 viewModelFactory: EmbeddedViewModelFactory, 91 @Main dispatcher: CoroutineDispatcher, 92 ): EmbeddedLifecycle { 93 // Force Lifecycle to be created on the MainDispatcher 94 val embeddedLifecycle = runBlocking(dispatcher) { EmbeddedLifecycle(viewModelFactory) } 95 return embeddedLifecycle 96 } 97 98 @Singleton 99 @Provides provideViewModelFactorynull100 fun provideViewModelFactory( 101 @Background backgroundDispatcher: CoroutineDispatcher, 102 featureManager: Lazy<FeatureManager>, 103 configurationManager: Lazy<ConfigurationManager>, 104 bannerManager: Lazy<BannerManager>, 105 selection: Lazy<Selection<Media>>, 106 userMonitor: Lazy<UserMonitor>, 107 dataService: Lazy<DataService>, 108 searchDataService: Lazy<SearchDataService>, 109 categoryDataService: Lazy<CategoryDataService>, 110 events: Lazy<Events>, 111 ): EmbeddedViewModelFactory { 112 val embeddedViewModelFactory = 113 EmbeddedViewModelFactory( 114 backgroundDispatcher, 115 configurationManager, 116 bannerManager, 117 dataService, 118 searchDataService, 119 categoryDataService, 120 events, 121 featureManager, 122 selection, 123 userMonitor, 124 ) 125 return embeddedViewModelFactory 126 } 127 128 @Singleton 129 @Provides provideBannerManagernull130 fun provideBannerManager( 131 @Background backgroundScope: CoroutineScope, 132 @Background backgroundDispatcher: CoroutineDispatcher, 133 configurationManager: ConfigurationManager, 134 databaseManager: DatabaseManager, 135 featureManager: FeatureManager, 136 dataService: DataService, 137 userMonitor: UserMonitor, 138 processOwnerHandle: UserHandle, 139 ): BannerManager { 140 return BannerManagerImpl( 141 backgroundScope, 142 backgroundDispatcher, 143 configurationManager, 144 databaseManager, 145 featureManager, 146 dataService, 147 userMonitor, 148 processOwnerHandle, 149 ) 150 } 151 152 @Singleton 153 @Provides createConfigurationManagernull154 fun createConfigurationManager( 155 @Background scope: CoroutineScope, 156 @Background dispatcher: CoroutineDispatcher, 157 deviceConfigProxy: DeviceConfigProxy, 158 ): ConfigurationManager { 159 160 return ConfigurationManager( 161 options.runtimeEnv, 162 scope, 163 dispatcher, 164 deviceConfigProxy, 165 generatePickerSessionId(), 166 ) 167 } 168 169 @Singleton 170 @Provides provideDatabaseManagernull171 fun provideDatabaseManager(): DatabaseManager { 172 return DatabaseManagerTestImpl() 173 } 174 175 /** Use a test DeviceConfigProxy to isolate device state */ 176 @Singleton 177 @Provides createDeviceConfigProxynull178 fun createDeviceConfigProxy(): DeviceConfigProxy { 179 return TestDeviceConfigProxyImpl() 180 } 181 182 @Singleton 183 @Provides createUserHandlenull184 fun createUserHandle(): UserHandle { 185 return options.processOwnerHandle 186 } 187 188 @Singleton 189 @Provides createUserMonitornull190 fun createUserMonitor( 191 context: Context, 192 configurationManager: ConfigurationManager, 193 @Background scope: CoroutineScope, 194 @Background dispatcher: CoroutineDispatcher, 195 userHandle: UserHandle, 196 ): UserMonitor { 197 return UserMonitor( 198 context, 199 configurationManager.configuration, 200 scope, 201 dispatcher, 202 userHandle, 203 ) 204 } 205 206 @Singleton 207 @Provides createDataServicenull208 fun createDataService(): DataService { 209 return TestDataServiceImpl() 210 } 211 212 @Singleton 213 @Provides createEventsnull214 fun createEvents( 215 @Background scope: CoroutineScope, 216 configurationManager: ConfigurationManager, 217 featureManager: FeatureManager, 218 ): Events { 219 return Events(scope = scope, configurationManager.configuration, featureManager) 220 } 221 222 @Singleton 223 @Provides createFeatureManagernull224 fun createFeatureManager( 225 @Background scope: CoroutineScope, 226 configurationManager: ConfigurationManager, 227 prefetchDataService: PrefetchDataService, 228 ): FeatureManager { 229 return FeatureManager( 230 configuration = configurationManager.configuration, 231 scope = scope, 232 prefetchDataService = prefetchDataService, 233 registeredFeatures = options.registeredFeatures, 234 dispatcher = Dispatchers.Main.immediate, 235 ) 236 } 237 238 @Singleton 239 @Provides createMediaProviderClientnull240 fun createMediaProviderClient(): MediaProviderClient { 241 return MediaProviderClient() 242 } 243 244 @Singleton 245 @Provides createPrefetchDataServicenull246 fun createPrefetchDataService(): PrefetchDataService { 247 return TestPrefetchDataService() 248 } 249 250 @Singleton 251 @Provides createSelectionnull252 fun createSelection( 253 @Background scope: CoroutineScope, 254 configurationManager: ConfigurationManager, 255 ): Selection<Media> { 256 return when (determineSelectionStrategy(configurationManager.configuration.value)) { 257 SelectionStrategy.GRANTS_AWARE_SELECTION -> 258 GrantsAwareSelectionImpl( 259 scope = scope, 260 configuration = configurationManager.configuration, 261 preGrantedItemsCount = TestDataServiceImpl().preGrantedMediaCount, 262 ) 263 SelectionStrategy.DEFAULT -> 264 SelectionImpl( 265 scope = scope, 266 configuration = configurationManager.configuration, 267 preSelectedMedia = TestDataServiceImpl().preSelectionMediaData, 268 ) 269 } 270 } 271 } 272