• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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