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.features.cloudmedia
18 
19 import android.app.Instrumentation.ActivityMonitor
20 import android.content.ContentResolver
21 import android.content.Context
22 import android.content.Intent
23 import android.content.IntentFilter
24 import android.content.pm.PackageManager
25 import android.os.UserManager
26 import android.provider.MediaStore
27 import android.test.mock.MockContentResolver
28 import androidx.compose.ui.test.ExperimentalTestApi
29 import androidx.compose.ui.test.assertIsDisplayed
30 import androidx.compose.ui.test.assertIsNotDisplayed
31 import androidx.compose.ui.test.hasContentDescription
32 import androidx.compose.ui.test.hasText
33 import androidx.compose.ui.test.junit4.createAndroidComposeRule
34 import androidx.compose.ui.test.performClick
35 import androidx.test.platform.app.InstrumentationRegistry
36 import com.android.photopicker.R
37 import com.android.photopicker.core.ActivityModule
38 import com.android.photopicker.core.ApplicationModule
39 import com.android.photopicker.core.ApplicationOwned
40 import com.android.photopicker.core.Background
41 import com.android.photopicker.core.ConcurrencyModule
42 import com.android.photopicker.core.EmbeddedServiceModule
43 import com.android.photopicker.core.Main
44 import com.android.photopicker.core.banners.BannerDefinitions
45 import com.android.photopicker.core.banners.BannerManager
46 import com.android.photopicker.core.banners.BannerState
47 import com.android.photopicker.core.banners.BannerStateDao
48 import com.android.photopicker.core.configuration.ConfigurationManager
49 import com.android.photopicker.core.configuration.DeviceConfigProxy
50 import com.android.photopicker.core.configuration.FEATURE_CLOUD_ENFORCE_PROVIDER_ALLOWLIST
51 import com.android.photopicker.core.configuration.FEATURE_CLOUD_MEDIA_FEATURE_ENABLED
52 import com.android.photopicker.core.configuration.FEATURE_CLOUD_MEDIA_PROVIDER_ALLOWLIST
53 import com.android.photopicker.core.configuration.NAMESPACE_MEDIAPROVIDER
54 import com.android.photopicker.core.configuration.PhotopickerFlags
55 import com.android.photopicker.core.configuration.TestDeviceConfigProxyImpl
56 import com.android.photopicker.core.configuration.TestPhotopickerConfiguration
57 import com.android.photopicker.core.database.DatabaseManager
58 import com.android.photopicker.core.events.Events
59 import com.android.photopicker.core.features.FeatureManager
60 import com.android.photopicker.core.glide.GlideTestRule
61 import com.android.photopicker.core.selection.Selection
62 import com.android.photopicker.data.DataService
63 import com.android.photopicker.data.TestDataServiceImpl
64 import com.android.photopicker.data.model.CollectionInfo
65 import com.android.photopicker.data.model.Media
66 import com.android.photopicker.data.model.MediaSource
67 import com.android.photopicker.data.model.Provider
68 import com.android.photopicker.features.PhotopickerFeatureBaseTest
69 import com.android.photopicker.features.overflowmenu.OverflowMenuFeature
70 import com.android.photopicker.inject.PhotopickerTestModule
71 import com.android.photopicker.tests.HiltTestActivity
72 import com.android.photopicker.util.test.nonNullableEq
73 import com.android.photopicker.util.test.whenever
74 import com.google.common.truth.Truth.assertWithMessage
75 import dagger.Lazy
76 import dagger.Module
77 import dagger.hilt.InstallIn
78 import dagger.hilt.android.testing.BindValue
79 import dagger.hilt.android.testing.HiltAndroidRule
80 import dagger.hilt.android.testing.HiltAndroidTest
81 import dagger.hilt.android.testing.UninstallModules
82 import dagger.hilt.components.SingletonComponent
83 import javax.inject.Inject
84 import kotlinx.coroutines.CoroutineDispatcher
85 import kotlinx.coroutines.CoroutineScope
86 import kotlinx.coroutines.ExperimentalCoroutinesApi
87 import kotlinx.coroutines.test.StandardTestDispatcher
88 import kotlinx.coroutines.test.TestScope
89 import kotlinx.coroutines.test.advanceTimeBy
90 import kotlinx.coroutines.test.runTest
91 import org.junit.Before
92 import org.junit.Rule
93 import org.junit.Test
94 import org.mockito.Mock
95 import org.mockito.Mockito.anyInt
96 import org.mockito.MockitoAnnotations
97 
98 @UninstallModules(
99     ActivityModule::class,
100     ApplicationModule::class,
101     ConcurrencyModule::class,
102     EmbeddedServiceModule::class,
103 )
104 @HiltAndroidTest
105 @OptIn(ExperimentalCoroutinesApi::class, ExperimentalTestApi::class)
106 class CloudMediaFeatureTest : PhotopickerFeatureBaseTest() {
107 
108     /* Hilt's rule needs to come first to ensure the DI container is setup for the test. */
109     @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
110     @get:Rule(order = 1)
111     val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
112     @get:Rule(order = 2) val glideRule = GlideTestRule()
113 
114     /* Setup dependencies for the UninstallModules for the test class. */
115     @Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
116 
117     val testDispatcher = StandardTestDispatcher()
118 
119     /* Overrides for ActivityModule */
120     val testScope: TestScope = TestScope(testDispatcher)
121     @BindValue @Main val mainScope: CoroutineScope = testScope
122     @BindValue @Background var testBackgroundScope: CoroutineScope = testScope.backgroundScope
123 
124     /* Overrides for the ConcurrencyModule */
125     @BindValue @Main val mainDispatcher: CoroutineDispatcher = testDispatcher
126     @BindValue @Background val backgroundDispatcher: CoroutineDispatcher = testDispatcher
127 
128     @BindValue @ApplicationOwned val contentResolver: ContentResolver = MockContentResolver()
129 
130     @Inject lateinit var selection: Lazy<Selection<Media>>
131     @Inject lateinit var featureManager: Lazy<FeatureManager>
132     @Inject lateinit var bannerManager: Lazy<BannerManager>
133     @Inject lateinit var events: Lazy<Events>
134     @Inject lateinit var dataService: Lazy<DataService>
135     @Inject lateinit var databaseManager: DatabaseManager
136     @Inject override lateinit var configurationManager: Lazy<ConfigurationManager>
137 
138     // Needed for UserMonitor
139     @Inject lateinit var mockContext: Context
140     @Inject lateinit var deviceConfig: DeviceConfigProxy
141     @Mock lateinit var mockUserManager: UserManager
142     @Mock lateinit var mockPackageManager: PackageManager
143 
144     private val localProvider =
145         Provider(
146             authority = "local_authority",
147             mediaSource = MediaSource.LOCAL,
148             uid = 1,
149             displayName = "Local Provider",
150         )
151     private val cloudProvider =
152         Provider(
153             authority = "clout_authority",
154             mediaSource = MediaSource.REMOTE,
155             uid = 2,
156             displayName = "Cloud Provider",
157         )
158 
159     @Before
setupnull160     fun setup() {
161         MockitoAnnotations.initMocks(this)
162         hiltRule.inject()
163 
164         val testDeviceConfigProxy =
165             checkNotNull(deviceConfig as? TestDeviceConfigProxyImpl) {
166                 "Expected a TestDeviceConfigProxy"
167             }
168 
169         testDeviceConfigProxy.setFlag(
170             NAMESPACE_MEDIAPROVIDER,
171             FEATURE_CLOUD_MEDIA_FEATURE_ENABLED.first,
172             true,
173         )
174         testDeviceConfigProxy.setFlag(
175             NAMESPACE_MEDIAPROVIDER,
176             FEATURE_CLOUD_ENFORCE_PROVIDER_ALLOWLIST.first,
177             true,
178         )
179         testDeviceConfigProxy.setFlag(
180             NAMESPACE_MEDIAPROVIDER,
181             FEATURE_CLOUD_MEDIA_PROVIDER_ALLOWLIST.first,
182             "com.android.test.cloudpicker",
183         )
184 
185         configurationManager
186             .get()
187             .setCaller(
188                 callingPackage = "com.android.test.package",
189                 callingPackageUid = 12345,
190                 callingPackageLabel = "Test Package",
191             )
192         // Stub for MockContentResolver constructor
193         whenever(mockContext.getApplicationInfo()) { getTestableContext().getApplicationInfo() }
194 
195         setupTestForUserMonitor(mockContext, mockUserManager, contentResolver, mockPackageManager)
196     }
197 
198     @Test
testCloudMediaEnabledInConfigurationsnull199     fun testCloudMediaEnabledInConfigurations() {
200         assertWithMessage("CloudMediaFeature is not always enabled (ACTION_PICK_IMAGES)")
201             .that(
202                 CloudMediaFeature.Registration.isEnabled(
203                     TestPhotopickerConfiguration.build {
204                         action(MediaStore.ACTION_PICK_IMAGES)
205                         intent(Intent(MediaStore.ACTION_PICK_IMAGES))
206                         flags(
207                             PhotopickerFlags(
208                                 CLOUD_MEDIA_ENABLED = true,
209                                 CLOUD_ALLOWED_PROVIDERS = arrayOf("cloud_authority"),
210                             )
211                         )
212                     }
213                 )
214             )
215             .isEqualTo(true)
216 
217         assertWithMessage("CloudMediaFeature is enabled with invalid flags (ACTION_PICK_IMAGES)")
218             .that(
219                 CloudMediaFeature.Registration.isEnabled(
220                     TestPhotopickerConfiguration.build {
221                         action(MediaStore.ACTION_PICK_IMAGES)
222                         intent(Intent(MediaStore.ACTION_PICK_IMAGES))
223                     }
224                 )
225             )
226             .isEqualTo(false)
227 
228         assertWithMessage("CloudMediaFeature is not always enabled (ACTION_GET_CONTENT)")
229             .that(
230                 CloudMediaFeature.Registration.isEnabled(
231                     TestPhotopickerConfiguration.build {
232                         action(Intent.ACTION_GET_CONTENT)
233                         intent(Intent(Intent.ACTION_GET_CONTENT))
234                         flags(
235                             PhotopickerFlags(
236                                 CLOUD_MEDIA_ENABLED = true,
237                                 CLOUD_ALLOWED_PROVIDERS = arrayOf("cloud_authority"),
238                             )
239                         )
240                     }
241                 )
242             )
243             .isEqualTo(true)
244 
245         assertWithMessage("CloudMediaFeature is enabled with invalid flags (ACTION_GET_CONTENT)")
246             .that(
247                 CloudMediaFeature.Registration.isEnabled(
248                     TestPhotopickerConfiguration.build {
249                         action(Intent.ACTION_GET_CONTENT)
250                         intent(Intent(Intent.ACTION_GET_CONTENT))
251                     }
252                 )
253             )
254             .isEqualTo(false)
255 
256         assertWithMessage("CloudMediaFeature is not always disabled (USER_SELECT_FOR_APP)")
257             .that(
258                 CloudMediaFeature.Registration.isEnabled(
259                     TestPhotopickerConfiguration.build {
260                         action(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP)
261                         intent(Intent(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP))
262                         callingPackage("com.example.test")
263                         callingPackageUid(1234)
264                         callingPackageLabel("test_app")
265                         flags(
266                             PhotopickerFlags(
267                                 CLOUD_MEDIA_ENABLED = true,
268                                 CLOUD_ALLOWED_PROVIDERS = arrayOf("cloud_authority"),
269                             )
270                         )
271                     }
272                 )
273             )
274             .isEqualTo(false)
275 
276         assertWithMessage(
277                 "CloudMediaFeature is not always disabled (default flags) (USER_SELECT_FOR_APP)"
278             )
279             .that(
280                 CloudMediaFeature.Registration.isEnabled(
281                     TestPhotopickerConfiguration.build {
282                         action(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP)
283                         intent(Intent(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP))
284                         callingPackage("com.example.test")
285                         callingPackageUid(1234)
286                         callingPackageLabel("test_app")
287                     }
288                 )
289             )
290             .isEqualTo(false)
291     }
292 
293     @Test
testCloudMediaOverflowMenuItemIsDisplayednull294     fun testCloudMediaOverflowMenuItemIsDisplayed() =
295         testScope.runTest {
296             composeTestRule.setContent {
297                 callPhotopickerMain(
298                     featureManager = featureManager.get(),
299                     selection = selection.get(),
300                     events = events.get(),
301                 )
302             }
303 
304             assertWithMessage("OverflowMenuFeature is not enabled")
305                 .that(featureManager.get().isFeatureEnabled(OverflowMenuFeature::class.java))
306                 .isTrue()
307 
308             composeTestRule
309                 .onNode(
310                     hasContentDescription(
311                         getTestableContext()
312                             .getResources()
313                             .getString(R.string.photopicker_overflow_menu_description)
314                     )
315                 )
316                 .performClick()
317 
318             composeTestRule
319                 .onNode(
320                     hasText(
321                         getTestableContext()
322                             .getResources()
323                             .getString(R.string.photopicker_overflow_cloud_media_app)
324                     )
325                 )
326                 .assertIsDisplayed()
327         }
328 
329     @Test
testCloudMediaOverflowMenuItemLaunchesCloudSettingsnull330     fun testCloudMediaOverflowMenuItemLaunchesCloudSettings() =
331         testScope.runTest {
332 
333             // Setup an intentFilter that matches the settings action
334             val intentFilter =
335                 IntentFilter().apply { addAction(MediaStore.ACTION_PICK_IMAGES_SETTINGS) }
336 
337             // Setup an activityMonitor to catch any launched intents to settings
338             val activityMonitor =
339                 ActivityMonitor(intentFilter, /* result= */ null, /* block= */ true)
340             InstrumentationRegistry.getInstrumentation().addMonitor(activityMonitor)
341 
342             composeTestRule.setContent {
343                 callPhotopickerMain(
344                     featureManager = featureManager.get(),
345                     selection = selection.get(),
346                     events = events.get(),
347                 )
348             }
349 
350             assertWithMessage("OverflowMenuFeature is not enabled")
351                 .that(featureManager.get().isFeatureEnabled(OverflowMenuFeature::class.java))
352                 .isTrue()
353 
354             composeTestRule
355                 .onNode(
356                     hasContentDescription(
357                         getTestableContext()
358                             .getResources()
359                             .getString(R.string.photopicker_overflow_menu_description)
360                     )
361                 )
362                 .performClick()
363 
364             composeTestRule
365                 .onNode(
366                     hasText(
367                         getTestableContext()
368                             .getResources()
369                             .getString(R.string.photopicker_overflow_cloud_media_app)
370                     )
371                 )
372                 .assertIsDisplayed()
373                 .performClick()
374 
375             activityMonitor.waitForActivityWithTimeout(5000L)
376             assertWithMessage("Settings activity wasn't launched")
377                 .that(activityMonitor.getHits())
378                 .isEqualTo(1)
379         }
380 
381     @Test
testCloudMediaAvailableBannernull382     fun testCloudMediaAvailableBanner() =
383         testScope.runTest {
384             val bannerStateDao = databaseManager.acquireDao(BannerStateDao::class.java)
385 
386             // Treat privacy explainer as already dismissed since it's a higher priority.
387             whenever(
388                 bannerStateDao.getBannerState(
389                     nonNullableEq(BannerDefinitions.PRIVACY_EXPLAINER.id),
390                     anyInt(),
391                 )
392             ) {
393                 BannerState(
394                     bannerId = BannerDefinitions.PRIVACY_EXPLAINER.id,
395                     dismissed = true,
396                     uid = 12345,
397                 )
398             }
399             whenever(
400                 bannerStateDao.getBannerState(
401                     nonNullableEq(BannerDefinitions.CLOUD_MEDIA_AVAILABLE.id),
402                     anyInt(),
403                 )
404             ) {
405                 null
406             }
407 
408             val testDataService = dataService.get() as? TestDataServiceImpl
409             checkNotNull(testDataService) { "Expected a TestDataServiceImpl" }
410             testDataService.setAvailableProviders(listOf(localProvider, cloudProvider))
411             testDataService.collectionInfo.put(
412                 cloudProvider,
413                 CollectionInfo(
414                     authority = cloudProvider.authority,
415                     collectionId = "collection-id",
416                     accountName = "[email protected]",
417                     accountConfigurationIntent = Intent(),
418                 ),
419             )
420 
421             val resources = getTestableContext().getResources()
422             val expectedTitle =
423                 resources.getString(R.string.photopicker_banner_cloud_media_available_title)
424             val expectedMessage =
425                 resources.getString(
426                     R.string.photopicker_banner_cloud_media_available_message,
427                     cloudProvider.displayName,
428                     "[email protected]",
429                 )
430 
431             bannerManager.get().refreshBanners()
432             advanceTimeBy(100)
433             composeTestRule.setContent {
434                 callPhotopickerMain(
435                     featureManager = featureManager.get(),
436                     selection = selection.get(),
437                     events = events.get(),
438                 )
439             }
440             composeTestRule.waitForIdle()
441             composeTestRule.onNode(hasText(expectedTitle)).assertIsDisplayed()
442             composeTestRule.onNode(hasText(expectedMessage)).assertIsDisplayed()
443         }
444 
445     @Test
testCloudMediaAvailableBannerAsDismissednull446     fun testCloudMediaAvailableBannerAsDismissed() =
447         testScope.runTest {
448             val bannerStateDao = databaseManager.acquireDao(BannerStateDao::class.java)
449 
450             // Treat privacy explainer as already dismissed since it's a higher priority.
451             whenever(
452                 bannerStateDao.getBannerState(
453                     nonNullableEq(BannerDefinitions.PRIVACY_EXPLAINER.id),
454                     anyInt(),
455                 )
456             ) {
457                 BannerState(
458                     bannerId = BannerDefinitions.PRIVACY_EXPLAINER.id,
459                     dismissed = true,
460                     uid = 12345,
461                 )
462             }
463             whenever(
464                 bannerStateDao.getBannerState(
465                     nonNullableEq(BannerDefinitions.CLOUD_MEDIA_AVAILABLE.id),
466                     anyInt(),
467                 )
468             ) {
469                 BannerState(
470                     bannerId = BannerDefinitions.CLOUD_MEDIA_AVAILABLE.id,
471                     dismissed = true,
472                     uid = 12345,
473                 )
474             }
475 
476             val testDataService = dataService.get() as? TestDataServiceImpl
477             checkNotNull(testDataService) { "Expected a TestDataServiceImpl" }
478             testDataService.setAvailableProviders(listOf(localProvider, cloudProvider))
479             testDataService.collectionInfo.put(
480                 cloudProvider,
481                 CollectionInfo(
482                     authority = cloudProvider.authority,
483                     collectionId = "collection-id",
484                     accountName = "[email protected]",
485                     accountConfigurationIntent = Intent(),
486                 ),
487             )
488 
489             val resources = getTestableContext().getResources()
490             val expectedTitle =
491                 resources.getString(R.string.photopicker_banner_cloud_media_available_title)
492             val expectedMessage =
493                 resources.getString(
494                     R.string.photopicker_banner_cloud_media_available_message,
495                     cloudProvider.displayName,
496                     "[email protected]",
497                 )
498 
499             bannerManager.get().refreshBanners()
500             advanceTimeBy(100)
501             composeTestRule.setContent {
502                 callPhotopickerMain(
503                     featureManager = featureManager.get(),
504                     selection = selection.get(),
505                     events = events.get(),
506                 )
507             }
508             composeTestRule.waitForIdle()
509             composeTestRule.onNode(hasText(expectedTitle)).assertIsNotDisplayed()
510             composeTestRule.onNode(hasText(expectedMessage)).assertIsNotDisplayed()
511         }
512 
513     @Test
testCloudChooseAccountBannernull514     fun testCloudChooseAccountBanner() =
515         testScope.runTest {
516             val bannerStateDao = databaseManager.acquireDao(BannerStateDao::class.java)
517 
518             // Treat privacy explainer as already dismissed since it's a higher priority.
519             whenever(
520                 bannerStateDao.getBannerState(
521                     nonNullableEq(BannerDefinitions.PRIVACY_EXPLAINER.id),
522                     anyInt(),
523                 )
524             ) {
525                 BannerState(
526                     bannerId = BannerDefinitions.PRIVACY_EXPLAINER.id,
527                     dismissed = true,
528                     uid = 12345,
529                 )
530             }
531 
532             val testDataService = dataService.get() as? TestDataServiceImpl
533             checkNotNull(testDataService) { "Expected a TestDataServiceImpl" }
534             testDataService.setAvailableProviders(listOf(localProvider, cloudProvider))
535             testDataService.collectionInfo.put(
536                 cloudProvider,
537                 CollectionInfo(
538                     authority = cloudProvider.authority,
539                     collectionId = null,
540                     accountName = null,
541                     accountConfigurationIntent = Intent(),
542                 ),
543             )
544 
545             val resources = getTestableContext().getResources()
546             val expectedTitle =
547                 resources.getString(R.string.photopicker_banner_cloud_choose_account_title)
548             val expectedMessage =
549                 resources.getString(
550                     R.string.photopicker_banner_cloud_choose_account_message,
551                     cloudProvider.displayName,
552                 )
553 
554             bannerManager.get().refreshBanners()
555             advanceTimeBy(100)
556             composeTestRule.setContent {
557                 callPhotopickerMain(
558                     featureManager = featureManager.get(),
559                     selection = selection.get(),
560                     events = events.get(),
561                 )
562             }
563             composeTestRule.waitForIdle()
564             composeTestRule.onNode(hasText(expectedTitle)).assertIsDisplayed()
565             composeTestRule.onNode(hasText(expectedMessage)).assertIsDisplayed()
566         }
567 
568     @Test
testCloudChooseAccountBannerAsDismissednull569     fun testCloudChooseAccountBannerAsDismissed() =
570         testScope.runTest {
571             val bannerStateDao = databaseManager.acquireDao(BannerStateDao::class.java)
572 
573             // Treat privacy explainer as already dismissed since it's a higher priority.
574             whenever(
575                 bannerStateDao.getBannerState(
576                     nonNullableEq(BannerDefinitions.PRIVACY_EXPLAINER.id),
577                     anyInt(),
578                 )
579             ) {
580                 BannerState(
581                     bannerId = BannerDefinitions.PRIVACY_EXPLAINER.id,
582                     dismissed = true,
583                     uid = 12345,
584                 )
585             }
586 
587             whenever(
588                 bannerStateDao.getBannerState(
589                     nonNullableEq(BannerDefinitions.CLOUD_CHOOSE_ACCOUNT.id),
590                     anyInt(),
591                 )
592             ) {
593                 BannerState(
594                     bannerId = BannerDefinitions.CLOUD_CHOOSE_ACCOUNT.id,
595                     dismissed = true,
596                     uid = 12345,
597                 )
598             }
599 
600             val testDataService = dataService.get() as? TestDataServiceImpl
601             checkNotNull(testDataService) { "Expected a TestDataServiceImpl" }
602             testDataService.setAvailableProviders(listOf(localProvider, cloudProvider))
603             testDataService.collectionInfo.put(
604                 cloudProvider,
605                 CollectionInfo(
606                     authority = cloudProvider.authority,
607                     collectionId = null,
608                     accountName = null,
609                     accountConfigurationIntent = Intent(),
610                 ),
611             )
612 
613             val resources = getTestableContext().getResources()
614             val expectedTitle =
615                 resources.getString(R.string.photopicker_banner_cloud_choose_account_title)
616             val expectedMessage =
617                 resources.getString(
618                     R.string.photopicker_banner_cloud_choose_account_message,
619                     cloudProvider.displayName,
620                 )
621 
622             bannerManager.get().refreshBanners()
623             advanceTimeBy(100)
624             composeTestRule.setContent {
625                 callPhotopickerMain(
626                     featureManager = featureManager.get(),
627                     selection = selection.get(),
628                     events = events.get(),
629                 )
630             }
631             composeTestRule.waitForIdle()
632             composeTestRule.onNode(hasText(expectedTitle)).assertIsNotDisplayed()
633             composeTestRule.onNode(hasText(expectedMessage)).assertIsNotDisplayed()
634         }
635 
636     @Test
testCloudChooseProviderBannernull637     fun testCloudChooseProviderBanner() =
638         testScope.runTest {
639             val bannerStateDao = databaseManager.acquireDao(BannerStateDao::class.java)
640 
641             // Treat privacy explainer as already dismissed since it's a higher priority.
642             whenever(
643                 bannerStateDao.getBannerState(
644                     nonNullableEq(BannerDefinitions.PRIVACY_EXPLAINER.id),
645                     anyInt(),
646                 )
647             ) {
648                 BannerState(
649                     bannerId = BannerDefinitions.PRIVACY_EXPLAINER.id,
650                     dismissed = true,
651                     uid = 12345,
652                 )
653             }
654 
655             val testDataService = dataService.get() as? TestDataServiceImpl
656             checkNotNull(testDataService) { "Expected a TestDataServiceImpl" }
657             testDataService.allowedProviders = listOf(cloudProvider)
658             testDataService.setAvailableProviders(listOf(localProvider))
659 
660             val resources = getTestableContext().getResources()
661             val expectedTitle =
662                 resources.getString(R.string.photopicker_banner_cloud_choose_provider_title)
663             val expectedMessage =
664                 resources.getString(R.string.photopicker_banner_cloud_choose_provider_message)
665 
666             bannerManager.get().refreshBanners()
667             advanceTimeBy(100)
668             composeTestRule.setContent {
669                 callPhotopickerMain(
670                     featureManager = featureManager.get(),
671                     selection = selection.get(),
672                     events = events.get(),
673                 )
674             }
675             composeTestRule.waitForIdle()
676             composeTestRule.onNode(hasText(expectedTitle)).assertIsDisplayed()
677             composeTestRule.onNode(hasText(expectedMessage)).assertIsDisplayed()
678         }
679 
680     @Test
testCloudChooseProviderBannerAsDismissednull681     fun testCloudChooseProviderBannerAsDismissed() =
682         testScope.runTest {
683             val bannerStateDao = databaseManager.acquireDao(BannerStateDao::class.java)
684 
685             // Treat privacy explainer as already dismissed since it's a higher priority.
686             whenever(
687                 bannerStateDao.getBannerState(
688                     nonNullableEq(BannerDefinitions.PRIVACY_EXPLAINER.id),
689                     anyInt(),
690                 )
691             ) {
692                 BannerState(
693                     bannerId = BannerDefinitions.PRIVACY_EXPLAINER.id,
694                     dismissed = true,
695                     uid = 12345,
696                 )
697             }
698 
699             whenever(
700                 bannerStateDao.getBannerState(
701                     nonNullableEq(BannerDefinitions.CLOUD_CHOOSE_PROVIDER.id),
702                     anyInt(),
703                 )
704             ) {
705                 BannerState(
706                     bannerId = BannerDefinitions.CLOUD_CHOOSE_PROVIDER.id,
707                     dismissed = true,
708                     uid = 12345,
709                 )
710             }
711 
712             val testDataService = dataService.get() as? TestDataServiceImpl
713             checkNotNull(testDataService) { "Expected a TestDataServiceImpl" }
714             testDataService.allowedProviders = listOf(cloudProvider)
715             testDataService.setAvailableProviders(listOf(localProvider))
716 
717             val resources = getTestableContext().getResources()
718             val expectedTitle =
719                 resources.getString(R.string.photopicker_banner_cloud_choose_provider_title)
720             val expectedMessage =
721                 resources.getString(R.string.photopicker_banner_cloud_choose_provider_message)
722 
723             bannerManager.get().refreshBanners()
724             advanceTimeBy(100)
725             composeTestRule.setContent {
726                 callPhotopickerMain(
727                     featureManager = featureManager.get(),
728                     selection = selection.get(),
729                     events = events.get(),
730                 )
731             }
732             composeTestRule.waitForIdle()
733             composeTestRule.onNode(hasText(expectedTitle)).assertIsNotDisplayed()
734             composeTestRule.onNode(hasText(expectedMessage)).assertIsNotDisplayed()
735         }
736 }
737