1 /*
2  * Copyright (C) 2021 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.providers.media.photopicker.viewmodel;
18 
19 import static android.provider.CloudMediaProviderContract.AlbumColumns;
20 import static android.provider.CloudMediaProviderContract.MediaColumns.AUTHORITY;
21 import static android.provider.CloudMediaProviderContract.MediaColumns.DATA;
22 import static android.provider.CloudMediaProviderContract.MediaColumns.DATE_TAKEN_MILLIS;
23 import static android.provider.CloudMediaProviderContract.MediaColumns.DURATION_MILLIS;
24 import static android.provider.CloudMediaProviderContract.MediaColumns.HEIGHT;
25 import static android.provider.CloudMediaProviderContract.MediaColumns.ID;
26 import static android.provider.CloudMediaProviderContract.MediaColumns.IS_FAVORITE;
27 import static android.provider.CloudMediaProviderContract.MediaColumns.MEDIA_STORE_URI;
28 import static android.provider.CloudMediaProviderContract.MediaColumns.MIME_TYPE;
29 import static android.provider.CloudMediaProviderContract.MediaColumns.ORIENTATION;
30 import static android.provider.CloudMediaProviderContract.MediaColumns.SIZE_BYTES;
31 import static android.provider.CloudMediaProviderContract.MediaColumns.STANDARD_MIME_TYPE_EXTENSION;
32 import static android.provider.CloudMediaProviderContract.MediaColumns.SYNC_GENERATION;
33 import static android.provider.CloudMediaProviderContract.MediaColumns.WIDTH;
34 
35 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
36 
37 import static com.android.providers.media.PickerUriResolver.REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI;
38 import static com.android.providers.media.photopicker.data.model.Item.ROW_ID;
39 import static com.android.providers.media.photopicker.ui.ItemsAction.ACTION_CLEAR_AND_UPDATE_LIST;
40 import static com.android.providers.media.photopicker.ui.ItemsAction.ACTION_VIEW_CREATED;
41 
42 import static com.google.common.truth.Truth.assertThat;
43 import static com.google.common.truth.Truth.assertWithMessage;
44 
45 import static org.junit.Assert.assertEquals;
46 import static org.junit.Assert.assertFalse;
47 import static org.junit.Assert.assertNotNull;
48 import static org.junit.Assert.assertNull;
49 import static org.junit.Assert.assertTrue;
50 import static org.junit.Assert.fail;
51 import static org.mockito.ArgumentMatchers.any;
52 import static org.mockito.ArgumentMatchers.eq;
53 import static org.mockito.Mockito.mock;
54 import static org.mockito.Mockito.spy;
55 import static org.mockito.Mockito.times;
56 import static org.mockito.Mockito.verify;
57 import static org.mockito.Mockito.when;
58 
59 import android.app.Application;
60 import android.content.ContentResolver;
61 import android.content.Context;
62 import android.content.Intent;
63 import android.database.Cursor;
64 import android.database.MatrixCursor;
65 import android.graphics.Color;
66 import android.net.Uri;
67 import android.os.Bundle;
68 import android.os.CancellationSignal;
69 import android.provider.MediaStore;
70 import android.text.format.DateUtils;
71 
72 import androidx.annotation.NonNull;
73 import androidx.annotation.Nullable;
74 import androidx.lifecycle.LiveData;
75 import androidx.test.filters.SdkSuppress;
76 
77 import com.android.modules.utils.build.SdkLevel;
78 import com.android.providers.media.TestConfigStore;
79 import com.android.providers.media.photopicker.DataLoaderThread;
80 import com.android.providers.media.photopicker.PickerSyncController;
81 import com.android.providers.media.photopicker.data.ItemsProvider;
82 import com.android.providers.media.photopicker.data.PaginationParameters;
83 import com.android.providers.media.photopicker.data.PickerResult;
84 import com.android.providers.media.photopicker.data.Selection;
85 import com.android.providers.media.photopicker.data.UserIdManager;
86 import com.android.providers.media.photopicker.data.UserManagerState;
87 import com.android.providers.media.photopicker.data.model.Category;
88 import com.android.providers.media.photopicker.data.model.Item;
89 import com.android.providers.media.photopicker.data.model.ModelTestUtils;
90 import com.android.providers.media.photopicker.data.model.RefreshRequest;
91 import com.android.providers.media.photopicker.data.model.UserId;
92 import com.android.providers.media.photopicker.espresso.PhotoPickerBaseTest;
93 
94 import com.google.android.collect.Lists;
95 
96 import org.junit.Before;
97 import org.junit.Rule;
98 import org.junit.Test;
99 import org.junit.runner.RunWith;
100 import org.junit.runners.Parameterized;
101 import org.mockito.Mock;
102 import org.mockito.MockitoAnnotations;
103 
104 import java.util.ArrayList;
105 import java.util.List;
106 import java.util.Objects;
107 import java.util.Set;
108 import java.util.concurrent.TimeUnit;
109 import java.util.stream.Collectors;
110 
111 @RunWith(Parameterized.class)
112 public class PickerViewModelTest {
113     private static final String FAKE_CATEGORY_NAME = "testCategoryName";
114     private static final String FAKE_ID = "5";
115     private static final Context sTargetContext = getInstrumentation().getTargetContext();
116     private static final String TEST_PACKAGE_NAME = "com.android.providers.media.tests";
117     private static final String CMP_AUTHORITY = "authority";
118     private static final String CMP_ACCOUNT_NAME = "account_name";
119 
120     @Rule
121     public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
122 
123     @Mock
124     private Application mApplication;
125 
126     private PickerViewModel mPickerViewModel;
127     private TestItemsProvider mItemsProvider;
128     private TestConfigStore mConfigStore;
129     private BannerManager mBannerManager;
130     private BannerController mBannerController;
131     @Parameterized.Parameter(0)
132     public boolean isPrivateSpaceEnabled;
133 
134     /**
135      * Parametrize values for {@code isPrivateSpaceEnabled} to run all the tests twice once with
136      * private space flag enabled and once with it disabled.
137      */
138     @Parameterized.Parameters(name = "privateSpaceEnabled={0}")
data()139     public static Iterable<?> data() {
140         return Lists.newArrayList(true, false);
141     }
142 
PickerViewModelTest()143     public PickerViewModelTest() {
144     }
145 
146     @Before
setUp()147     public void setUp() {
148         MockitoAnnotations.initMocks(this);
149 
150         when(mApplication.getApplicationContext()).thenReturn(sTargetContext);
151         mConfigStore = new TestConfigStore();
152         mConfigStore.enableCloudMediaFeatureAndSetAllowedCloudProviderPackages(TEST_PACKAGE_NAME);
153         mConfigStore.enablePickerChoiceManagedSelectionEnabled();
154         if (isPrivateSpaceEnabled) {
155             mConfigStore.enablePrivateSpaceInPhotoPicker();
156         } else {
157             mConfigStore.disablePrivateSpaceInPhotoPicker();
158         }
159 
160         getInstrumentation().runOnMainSync(() -> {
161             mPickerViewModel = new PickerViewModel(mApplication) {
162                 @Override
163                 protected void initConfigStore() {
164                     setConfigStore(mConfigStore);
165                 }
166             };
167         });
168         mItemsProvider = new TestItemsProvider(sTargetContext);
169         mPickerViewModel.setItemsProvider(mItemsProvider);
170 
171         // set current user profile and banner manager
172         if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
173             final UserManagerState userManagerState = mock(UserManagerState.class);
174             when(userManagerState.getCurrentUserProfileId()).thenReturn(UserId.CURRENT_USER);
175             mPickerViewModel.setUserManagerState(userManagerState);
176             mBannerManager = BannerTestUtils.getTestCloudBannerManager(
177                     sTargetContext, userManagerState, mConfigStore);
178         } else {
179             final UserIdManager userIdManager = mock(UserIdManager.class);
180             when(userIdManager.getCurrentUserProfileId()).thenReturn(UserId.CURRENT_USER);
181             mPickerViewModel.setUserIdManager(userIdManager);
182             mBannerManager = BannerTestUtils.getTestCloudBannerManager(
183                     sTargetContext, userIdManager, mConfigStore);
184         }
185 
186         mPickerViewModel.setBannerManager(mBannerManager);
187 
188         // Set default banner manager values
189         mBannerController = mBannerManager.getBannerControllersPerUser().get(
190                 UserId.CURRENT_USER.getIdentifier());
191         assertNotNull(mBannerController);
192         mBannerController.onChangeCloudMediaInfo(
193                 /* cmpAuthority= */ null, /* cmpAccountName= */ null);
194         mBannerManager.maybeInitialiseAndSetBannersForCurrentUser();
195     }
196 
197     @Test
testGetItems_noItems()198     public void testGetItems_noItems() {
199         final int itemCount = 0;
200         mItemsProvider.setItems(generateFakeImageItemList(itemCount));
201         mPickerViewModel.getPaginatedItemsForAction(
202                 ACTION_CLEAR_AND_UPDATE_LIST, null);
203         // We use DataLoader thread to execute the loadItems in updateItems(), wait for the thread
204         // idle
205         DataLoaderThread.waitForIdle();
206 
207         final List<Item> itemList = Objects.requireNonNull(
208                 mPickerViewModel.getPaginatedItemsForAction(
209                         ACTION_VIEW_CREATED,
210                         new PaginationParameters()).getValue()).getItems();
211 
212         // No date headers, the size should be 0
213         assertThat(itemList.size()).isEqualTo(itemCount);
214     }
215 
216     @Test
testGetCategories()217     public void testGetCategories() throws Exception {
218         final int categoryCount = 2;
219         try (final Cursor fakeCursor = generateCursorForFakeCategories(categoryCount)) {
220             fakeCursor.moveToFirst();
221             final Category fakeFirstCategory = Category.fromCursor(fakeCursor, UserId.CURRENT_USER);
222             fakeCursor.moveToNext();
223             final Category fakeSecondCategory = Category.fromCursor(fakeCursor,
224                     UserId.CURRENT_USER);
225             mItemsProvider.setCategoriesCursor(fakeCursor);
226             // move the cursor to original position
227             fakeCursor.moveToPosition(-1);
228             mPickerViewModel.updateCategories();
229             // We use DataLoaderThread to execute the loadCategories in updateCategories(), wait for
230             // the thread idle
231             DataLoaderThread.waitForIdle();
232 
233             final List<Category> categoryList = mPickerViewModel.getCategories().getValue();
234 
235             assertThat(categoryList.size()).isEqualTo(categoryCount);
236             // Verify the first category
237             final Category firstCategory = categoryList.get(0);
238             assertThat(firstCategory.getDisplayName(sTargetContext)).isEqualTo(
239                     fakeFirstCategory.getDisplayName(sTargetContext));
240             assertThat(firstCategory.getItemCount()).isEqualTo(fakeFirstCategory.getItemCount());
241             assertThat(firstCategory.getCoverUri()).isEqualTo(fakeFirstCategory.getCoverUri());
242             // Verify the second category
243             final Category secondCategory = categoryList.get(1);
244             assertThat(secondCategory.getDisplayName(sTargetContext)).isEqualTo(
245                     fakeSecondCategory.getDisplayName(sTargetContext));
246             assertThat(secondCategory.getItemCount()).isEqualTo(fakeSecondCategory.getItemCount());
247             assertThat(secondCategory.getCoverUri()).isEqualTo(fakeSecondCategory.getCoverUri());
248         }
249     }
250 
251     @Test
test_getItems_correctItemsReturned()252     public void test_getItems_correctItemsReturned() {
253         final int numberOfTestItems = 4;
254         final List<Item> expectedItems = generateFakeImageItemList(numberOfTestItems);
255         mItemsProvider.setItems(expectedItems);
256 
257         LiveData<PickerViewModel.PaginatedItemsResult> testItems =
258                 mPickerViewModel.getPaginatedItemsForAction(
259                         ACTION_VIEW_CREATED,
260                         new PaginationParameters());
261         DataLoaderThread.waitForIdle();
262 
263         assertThat(testItems).isNotNull();
264         assertThat(testItems.getValue()).isNotNull();
265         assertThat(testItems.getValue().getItems().size()).isEqualTo(numberOfTestItems);
266 
267         for (int itr = 0; itr < numberOfTestItems; itr++) {
268             // Assert that all test and expected items are equal.
269             assertThat(testItems.getValue().getItems().get(itr).compareTo(
270                     expectedItems.get(itr))).isEqualTo(0);
271         }
272     }
273 
274     @SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
275     @Test
test_getRemainingPreGrantedItems_correctItemsLoaded()276     public void test_getRemainingPreGrantedItems_correctItemsLoaded() {
277         // Enable managed selection for this test.
278         Intent intent = new Intent(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP);
279         intent.putExtra(Intent.EXTRA_UID, 0);
280         mPickerViewModel.parseValuesFromIntent(intent);
281 
282         final int numberOfTestItems = 4;
283         final List<Item> expectedItems = generateFakeImageItemList(numberOfTestItems);
284         for (Item item : expectedItems) {
285             item.setPreGranted();
286         }
287         mItemsProvider.setItems(expectedItems);
288         List<Uri> preGrantedItems = List.of(expectedItems.get(0).getContentUri(),
289                 expectedItems.get(1).getContentUri(),
290                 expectedItems.get(2).getContentUri());
291         Selection selection = mPickerViewModel.getSelection();
292         // Add 3 item ids is preGranted set.
293         selection.setPreGrantedItems(preGrantedItems);
294 
295         // adding 1 item in selection item set.
296         selection.addSelectedItem(expectedItems.get(1));
297 
298         // revoking grant for 1 id.
299         selection.removeSelectedItem(expectedItems.get(0));
300 
301         // since only one item is added in selection set, the size should be one.
302         assertThat(selection.getSelectedItems().size()).isEqualTo(1);
303 
304         // Since out of 3 one grant was removed, so there would be one item loaded when remaining
305         // grants are loaded.
306         mPickerViewModel.getRemainingPreGrantedItems();
307         DataLoaderThread.waitForIdle();
308 
309         // Now the selection set should have 2 items.
310         assertThat(selection.getSelectedItems().size()).isEqualTo(2);
311     }
312 
313     @SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
314     @Test
test_deselectPreGrantedItem_correctRevokeMapMaintained()315     public void test_deselectPreGrantedItem_correctRevokeMapMaintained() {
316         // Enable managed selection for this test.
317         Intent intent = new Intent(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP);
318         intent.putExtra(Intent.EXTRA_UID, 0);
319         mPickerViewModel.parseValuesFromIntent(intent);
320 
321         final int numberOfTestItems = 4;
322         final List<Item> expectedItems = generateFakeImageItemList(numberOfTestItems);
323         for (Item item : expectedItems) {
324             item.setPreGranted();
325         }
326         mItemsProvider.setItems(expectedItems);
327 
328         List<Uri> preGrantedItems = List.of(
329                 expectedItems.get(0).getContentUri(),
330                 expectedItems.get(1).getContentUri(),
331                 expectedItems.get(2).getContentUri());
332 
333 
334         Selection selection = mPickerViewModel.getSelection();
335         // Add 3 item ids is preGranted set.
336         selection.setPreGrantedItems(preGrantedItems);
337 
338         // adding 2 items in selection item set.
339         selection.addSelectedItem(expectedItems.get(0));
340         selection.addSelectedItem(expectedItems.get(1));
341 
342         // revoking grant for the 0th item id.
343         selection.removeSelectedItem(expectedItems.get(0));
344 
345         // since only one item is added in selection set, the size should be one.
346         assertThat(selection.getSelectedItems().size()).isEqualTo(1);
347 
348         // verify revoked item is present in the items to be revoked set.
349         Set<Item> itemsToBeRevoked = selection.getDeselectedItemsToBeRevoked();
350         assertThat(itemsToBeRevoked.size()).isEqualTo(1);
351         assertThat(itemsToBeRevoked.contains(expectedItems.get(0))).isTrue();
352 
353         Set<Uri> itemUrisToBeRevoked = selection.getDeselectedUrisToBeRevoked();
354         assertThat(itemUrisToBeRevoked.size()).isEqualTo(1);
355         assertThat(itemUrisToBeRevoked.contains(expectedItems.get(0).getContentUri())).isTrue();
356     }
357 
358     @Test
test_initialisePreSelectionItems_correctItemsLoaded()359     public void test_initialisePreSelectionItems_correctItemsLoaded() {
360         // Set the intent action as PICK_IMAGES
361         Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
362         Bundle extras = new Bundle();
363         extras.putInt(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
364         intent.putExtras(extras);
365 
366         mPickerViewModel.parseValuesFromIntent(intent);
367 
368         // generate test items
369         final int numberOfTestItems = 4;
370         final List<Item> expectedItems = generateFakeImageItemList(numberOfTestItems);
371 
372 
373         // Mock the test items to return the required URI and id when used.
374         final List<Item> mockedExpectedItems = new ArrayList<>();
375         for (int i = 0; i < expectedItems.size(); i++) {
376             Item item = mock(Item.class);
377             when(item.getContentUri()).thenReturn(ItemsProvider.getItemsUri(
378                     expectedItems.get(i).getId(),
379                     PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY,
380                     UserId.CURRENT_USER));
381             when(item.getId()).thenReturn(expectedItems.get(i).getId());
382             mockedExpectedItems.add(item);
383         }
384         mItemsProvider.setItems(mockedExpectedItems);
385 
386         // generate a list of input pre-selected picker URI and add them to test intent extras.
387         ArrayList<Uri> preGrantedPickerUris = new ArrayList<>();
388         for (int i = 0; i < expectedItems.size(); i++) {
389             preGrantedPickerUris.add(
390                     PickerResult.getPickerUrisForItems(MediaStore.ACTION_PICK_IMAGES,
391                             List.of(mockedExpectedItems.get(i))).get(0));
392         }
393         Bundle intentExtras = new Bundle();
394         intentExtras.putParcelableArrayList(MediaStore.EXTRA_PICKER_PRE_SELECTION_URIS,
395                 preGrantedPickerUris);
396 
397         Selection selection = mPickerViewModel.getSelection();
398         // Since no item has been selected and no pre-granted URIs have been loaded, thus the size
399         // of selection should be 0.
400         assertThat(selection.getSelectedItems().size()).isEqualTo(0);
401 
402         DataLoaderThread.waitForIdle();
403 
404         // Initialise pre-granted items for selection.
405         mPickerViewModel.initialisePreGrantsIfNecessary(selection, intentExtras,
406                 /* mimeTypeFilters */ null);
407         DataLoaderThread.waitForIdle();
408 
409         // after initialization the items should have been added to selection.
410         assertThat(selection.getPreGrantedUris()).isNotNull();
411         assertThat(selection.getPreGrantedUris().size()).isEqualTo(4);
412         assertThat(mPickerViewModel.getSelection().getSelectedItems().size()).isEqualTo(4);
413     }
414 
415 
416     @Test
test_preSelectionItemsExceedMaxLimit_initialisationOfItemsFails()417     public void test_preSelectionItemsExceedMaxLimit_initialisationOfItemsFails() {
418         // Generate a list of test uris, the size being 2 uris more than the max number of URIs
419         // accepted.
420         String testUriPrefix = "content://media/picker/0/com.test.package/media/";
421         int numberOfPreselectedUris = MediaStore.getPickImagesMaxLimit() + 2;
422         ArrayList<Uri> testUrisAsString = new ArrayList<>();
423         for (int i = 0; i < numberOfPreselectedUris; i++) {
424             testUrisAsString.add(Uri.parse(testUriPrefix + String.valueOf(i)));
425         }
426 
427         // set up the intent extras to contain the test uris. Also, parse a test PICK_IMAGES intent
428         // to ensure that PickerViewModel works in PICK_IMAGES action mode.
429         Bundle intentExtras = new Bundle();
430         intentExtras.putInt(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
431         intentExtras.putParcelableArrayList(MediaStore.EXTRA_PICKER_PRE_SELECTION_URIS,
432                 testUrisAsString);
433         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
434         intent.putExtras(intentExtras);
435         mPickerViewModel.parseValuesFromIntent(intent);
436 
437         try {
438             mPickerViewModel.initialisePreGrantsIfNecessary(null, intentExtras, null);
439             fail("The initialisation of items should have failed since the number of pre-selected"
440                     + "items exceeds the max limit");
441         } catch (IllegalArgumentException illegalArgumentException) {
442             assertThat(illegalArgumentException.getMessage()).isEqualTo(
443                     "The number of URIs exceed the maximum allowed limit: "
444                             + MediaStore.getPickImagesMaxLimit());
445         }
446     }
447 
generateFakeImageItem(String id)448     private static Item generateFakeImageItem(String id) {
449         final long dateTakenMs = System.currentTimeMillis()
450                 + Long.parseLong(id) * DateUtils.DAY_IN_MILLIS;
451         return ModelTestUtils.generateJpegItem(id, dateTakenMs, /* generationModified */ 1L);
452     }
453 
generateFakeImageItemList(int num)454     private static List<Item> generateFakeImageItemList(int num) {
455         final List<Item> itemList = new ArrayList<>();
456         for (int i = 0; i < num; i++) {
457             itemList.add(generateFakeImageItem(String.valueOf(i)));
458         }
459         return itemList;
460     }
461 
generateCursorForFakeCategories(int num)462     private static Cursor generateCursorForFakeCategories(int num) {
463         final MatrixCursor cursor = new MatrixCursor(AlbumColumns.ALL_PROJECTION);
464         final int itemCount = 5;
465         for (int i = 0; i < num; i++) {
466             cursor.addRow(new Object[]{
467                     FAKE_ID + String.valueOf(i),
468                     System.currentTimeMillis(),
469                     FAKE_CATEGORY_NAME + i,
470                     FAKE_ID + String.valueOf(i),
471                     itemCount + i,
472                     PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY
473             });
474         }
475         return cursor;
476     }
477 
478     private static class TestItemsProvider extends ItemsProvider {
479 
480         private List<Item> mItemList = new ArrayList<>();
481         private Cursor mCategoriesCursor;
482 
TestItemsProvider(Context context)483         public TestItemsProvider(Context context) {
484             super(context);
485         }
486 
487         @Override
getAllItems(Category category, PaginationParameters paginationParameters, @Nullable String[] mimeType, @Nullable UserId userId, @Nullable CancellationSignal cancellationSignal)488         public Cursor getAllItems(Category category,
489                 PaginationParameters paginationParameters, @Nullable String[] mimeType,
490                 @Nullable UserId userId,
491                 @Nullable CancellationSignal cancellationSignal) throws
492                 IllegalArgumentException, IllegalStateException {
493             final String[] all_projection = new String[]{
494                     ID,
495                     // This field is unique to the cursor received by the pickerVIewModel.
496                     // It is not a part of cloud provider contract.
497                     ROW_ID,
498                     DATE_TAKEN_MILLIS,
499                     SYNC_GENERATION,
500                     MIME_TYPE,
501                     STANDARD_MIME_TYPE_EXTENSION,
502                     SIZE_BYTES,
503                     MEDIA_STORE_URI,
504                     DURATION_MILLIS,
505                     IS_FAVORITE,
506                     WIDTH,
507                     HEIGHT,
508                     ORIENTATION,
509                     DATA,
510                     AUTHORITY,
511             };
512             final MatrixCursor c = new MatrixCursor(all_projection);
513 
514             int itr = 1;
515             for (Item item : mItemList) {
516                 c.addRow(new String[]{
517                         item.getId(),
518                         String.valueOf(itr),
519                         String.valueOf(item.getDateTaken()),
520                         String.valueOf(item.getGenerationModified()),
521                         item.getMimeType(),
522                         String.valueOf(item.getSpecialFormat()),
523                         "1", // size_bytes
524                         null, // media_store_uri
525                         String.valueOf(item.getDuration()),
526                         "0", // is_favorite
527                         String.valueOf(800), // width
528                         String.valueOf(500), // height
529                         String.valueOf(0), // orientation
530                         "/storage/emulated/0/foo",
531                         PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY
532                 });
533                 itr++;
534             }
535 
536             return c;
537         }
538 
539         @Override
getLocalItems(Category category, PaginationParameters paginationParameters, @Nullable String[] mimeType, @Nullable UserId userId, @Nullable CancellationSignal cancellationSignal)540         public Cursor getLocalItems(Category category,
541                 PaginationParameters paginationParameters, @Nullable String[] mimeType,
542                 @Nullable UserId userId,
543                 @Nullable CancellationSignal cancellationSignal) throws
544                 IllegalArgumentException, IllegalStateException {
545             final String[] all_projection = new String[]{
546                     ID,
547                     // This field is unique to the cursor received by the pickerVIewModel.
548                     // It is not a part of cloud provider contract.
549                     ROW_ID,
550                     DATE_TAKEN_MILLIS,
551                     SYNC_GENERATION,
552                     MIME_TYPE,
553                     STANDARD_MIME_TYPE_EXTENSION,
554                     SIZE_BYTES,
555                     MEDIA_STORE_URI,
556                     DURATION_MILLIS,
557                     IS_FAVORITE,
558                     WIDTH,
559                     HEIGHT,
560                     ORIENTATION,
561                     DATA,
562                     AUTHORITY,
563             };
564             final MatrixCursor c = new MatrixCursor(all_projection);
565 
566             int itr = 1;
567             for (Item item : mItemList) {
568                 c.addRow(new String[]{
569                         item.getId(),
570                         String.valueOf(itr),
571                         String.valueOf(item.getDateTaken()),
572                         String.valueOf(item.getGenerationModified()),
573                         item.getMimeType(),
574                         String.valueOf(item.getSpecialFormat()),
575                         "1", // size_bytes
576                         null, // media_store_uri
577                         String.valueOf(item.getDuration()),
578                         "0", // is_favorite
579                         String.valueOf(800), // width
580                         String.valueOf(500), // height
581                         String.valueOf(0), // orientation
582                         "/storage/emulated/0/foo",
583                         PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY
584                 });
585                 itr++;
586             }
587 
588             return c;
589         }
590 
591         @Override
getItemsForPreselectedMedia(Category category, @NonNull List<Uri> preselectedUris, @Nullable String[] mimeTypes, @Nullable UserId userId, boolean isLocalOnly, int callingPackageUid, boolean shouldScreenSelectionUris, @Nullable CancellationSignal cancellationSignal)592         public Cursor getItemsForPreselectedMedia(Category category, @NonNull List<Uri>
593                 preselectedUris, @Nullable String[] mimeTypes, @Nullable UserId userId,
594                 boolean isLocalOnly, int callingPackageUid, boolean shouldScreenSelectionUris,
595                 @Nullable CancellationSignal cancellationSignal) throws IllegalArgumentException {
596             final String[] all_projection = new String[]{
597                     ID,
598                     // This field is unique to the cursor received by the pickerVIewModel.
599                     // It is not a part of cloud provider contract.
600                     ROW_ID,
601                     DATE_TAKEN_MILLIS,
602                     SYNC_GENERATION,
603                     MIME_TYPE,
604                     STANDARD_MIME_TYPE_EXTENSION,
605                     SIZE_BYTES,
606                     MEDIA_STORE_URI,
607                     DURATION_MILLIS,
608                     IS_FAVORITE,
609                     WIDTH,
610                     HEIGHT,
611                     ORIENTATION,
612                     DATA,
613                     AUTHORITY,
614             };
615             final MatrixCursor c = new MatrixCursor(all_projection);
616             List<String> preSelectedIds = preselectedUris.stream().map(
617                     Uri::getLastPathSegment).collect(Collectors.toList());
618             int itr = 1;
619             for (Item item : mItemList) {
620                 if (preSelectedIds.contains(item.getId())) {
621                     c.addRow(new String[]{
622                             item.getId(),
623                             String.valueOf(itr),
624                             String.valueOf(item.getDateTaken()),
625                             String.valueOf(item.getGenerationModified()),
626                             item.getMimeType(),
627                             String.valueOf(item.getSpecialFormat()),
628                             "1", // size_bytes
629                             null, // media_store_uri
630                             String.valueOf(item.getDuration()),
631                             "0", // is_favorite
632                             String.valueOf(800), // width
633                             String.valueOf(500), // height
634                             String.valueOf(0), // orientation
635                             "/storage/emulated/0/foo",
636                             PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY
637                     });
638                     itr++;
639                 }
640             }
641             return c;
642 
643         }
644 
645         @Nullable
getAllCategories(@ullable String[] mimeType, @Nullable UserId userId, @Nullable CancellationSignal cancellationSignal)646         public Cursor getAllCategories(@Nullable String[] mimeType, @Nullable UserId userId,
647                 @Nullable CancellationSignal cancellationSignal) {
648             if (mCategoriesCursor != null) {
649                 return mCategoriesCursor;
650             }
651 
652             final MatrixCursor c = new MatrixCursor(AlbumColumns.ALL_PROJECTION);
653             return c;
654         }
655 
setItems(@onNull List<Item> itemList)656         public void setItems(@NonNull List<Item> itemList) {
657             mItemList = itemList;
658         }
659 
setCategoriesCursor(@onNull Cursor cursor)660         public void setCategoriesCursor(@NonNull Cursor cursor) {
661             mCategoriesCursor = cursor;
662         }
663     }
664 
665     @Test
testParseValuesFromPickImagesIntent_noMimeType_defaultFalse()666     public void testParseValuesFromPickImagesIntent_noMimeType_defaultFalse() {
667         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
668 
669         mPickerViewModel.parseValuesFromIntent(intent);
670 
671         assertThat(mPickerViewModel.hasMimeTypeFilters()).isFalse();
672     }
673 
674     @Test
testParseValuesFromGetContentIntent_noMimeType_defaultFalse()675     public void testParseValuesFromGetContentIntent_noMimeType_defaultFalse() {
676         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
677         intent.setType("*/*");
678 
679         mPickerViewModel.parseValuesFromIntent(intent);
680 
681         assertThat(mPickerViewModel.hasMimeTypeFilters()).isFalse();
682     }
683 
684     @Test
testParseValuesFromIntent_validMimeType()685     public void testParseValuesFromIntent_validMimeType() {
686         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
687         intent.setType("image/png");
688 
689         mPickerViewModel.parseValuesFromIntent(intent);
690 
691         assertThat(mPickerViewModel.hasMimeTypeFilters()).isTrue();
692     }
693 
694     @Test
testParseValuesFromPickImagesIntent_validExtraMimeType()695     public void testParseValuesFromPickImagesIntent_validExtraMimeType() {
696         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
697         intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/gif", "video/*"});
698 
699         mPickerViewModel.parseValuesFromIntent(intent);
700 
701         assertThat(mPickerViewModel.hasMimeTypeFilters()).isTrue();
702     }
703 
704     @Test
testParseValuesFromPickImagesIntent_invalidExtraMimeType()705     public void testParseValuesFromPickImagesIntent_invalidExtraMimeType() {
706         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
707         intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"audio/*", "video/*"});
708 
709         try {
710             mPickerViewModel.parseValuesFromIntent(intent);
711             fail("Photo Picker does not support non-media mime type filters");
712         } catch (IllegalArgumentException expected) {
713             // Expected
714         }
715     }
716 
717     @Test
testParseValuesFromPickImagesIntent_localOnlyTrue()718     public void testParseValuesFromPickImagesIntent_localOnlyTrue() {
719         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
720         intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
721 
722         mPickerViewModel.parseValuesFromIntent(intent);
723 
724         assertThat(mPickerViewModel.isLocalOnly()).isTrue();
725     }
726 
727     @Test
testParseValuesFromPickImagesIntent_localOnlyFalse()728     public void testParseValuesFromPickImagesIntent_localOnlyFalse() {
729         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
730 
731         mPickerViewModel.parseValuesFromIntent(intent);
732 
733         assertThat(mPickerViewModel.isLocalOnly()).isFalse();
734     }
735 
736     @Test
testParseValuesFromGetContentIntent_validExtraMimeType()737     public void testParseValuesFromGetContentIntent_validExtraMimeType() {
738         final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
739         intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/gif", "video/*"});
740 
741         mPickerViewModel.parseValuesFromIntent(intent);
742 
743         assertThat(mPickerViewModel.hasMimeTypeFilters()).isTrue();
744     }
745 
746     @Test
testParseValuesFromGetContentIntent_invalidExtraMimeType()747     public void testParseValuesFromGetContentIntent_invalidExtraMimeType() {
748         final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
749         intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"audio/*", "video/*"});
750 
751         mPickerViewModel.parseValuesFromIntent(intent);
752 
753         // non-media filters for GET_CONTENT show all images and videos
754         assertThat(mPickerViewModel.hasMimeTypeFilters()).isFalse();
755     }
756 
757     @Test
testParseValuesFromGetContentIntent_localOnlyTrue()758     public void testParseValuesFromGetContentIntent_localOnlyTrue() {
759         final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
760         intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"video/*"});
761         intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
762 
763         mPickerViewModel.parseValuesFromIntent(intent);
764 
765         assertThat(mPickerViewModel.isLocalOnly()).isTrue();
766     }
767 
768     @Test
testParseValuesFromGetContentIntent_localOnlyFalse()769     public void testParseValuesFromGetContentIntent_localOnlyFalse() {
770         final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
771         intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"video/*"});
772 
773         mPickerViewModel.parseValuesFromIntent(intent);
774 
775         assertThat(mPickerViewModel.isLocalOnly()).isFalse();
776     }
777 
778     @Test
testParseValuesFromPickImagesIntent_launchPickerInPhotosTab()779     public void testParseValuesFromPickImagesIntent_launchPickerInPhotosTab() {
780         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
781         intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB, MediaStore.PICK_IMAGES_TAB_IMAGES);
782 
783         mPickerViewModel.parseValuesFromIntent(intent);
784 
785         assertThat(mPickerViewModel.getPickerLaunchTab()).isEqualTo(
786                 MediaStore.PICK_IMAGES_TAB_IMAGES);
787     }
788 
789     @Test
testParseValuesFromPickImagesIntent_launchPickerInAlbumsTab()790     public void testParseValuesFromPickImagesIntent_launchPickerInAlbumsTab() {
791         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
792         intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB, MediaStore.PICK_IMAGES_TAB_ALBUMS);
793 
794         mPickerViewModel.parseValuesFromIntent(intent);
795 
796         assertThat(mPickerViewModel.getPickerLaunchTab()).isEqualTo(
797                 MediaStore.PICK_IMAGES_TAB_ALBUMS);
798     }
799 
800     @Test
testParseValuesFromPickImagesIntent_launchPickerWithIncorrectTabOption()801     public void testParseValuesFromPickImagesIntent_launchPickerWithIncorrectTabOption() {
802         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
803         intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB, 2);
804 
805         try {
806             mPickerViewModel.parseValuesFromIntent(intent);
807             fail("Incorrect value passed for the picker launch tab option in the intent");
808         } catch (IllegalArgumentException expected) {
809             // Expected
810         }
811     }
812 
813     @Test
testParseValuesFromPickImagesIntent_validAccentColor()814     public void testParseValuesFromPickImagesIntent_validAccentColor() {
815         long accentColor = 0xFFFF0000;
816         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
817         intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_ACCENT_COLOR, accentColor);
818 
819         mPickerViewModel.parseValuesFromIntent(intent);
820 
821         assertThat(
822                 mPickerViewModel.getPickerAccentColorParameters().getPickerAccentColor()).isEqualTo(
823                         Color.parseColor(String.format("#%06X", (0xFFFFFF & accentColor))));
824         assertThat(
825                 mPickerViewModel.getPickerAccentColorParameters().isCustomPickerColorSet())
826                 .isTrue();
827     }
828 
829     @Test
testParseValuesFromPickImagesIntent_invalidAccentColor()830     public void testParseValuesFromPickImagesIntent_invalidAccentColor() {
831         long accentColor = 0xFF6;
832 
833         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
834         intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_ACCENT_COLOR, accentColor);
835 
836         try {
837             mPickerViewModel.parseValuesFromIntent(intent);
838         } catch (IllegalArgumentException e) {
839             // Expected
840         }
841     }
842 
843     @Test
testParseValuesFromGetContentIntent_extraPickerLaunchTab()844     public void testParseValuesFromGetContentIntent_extraPickerLaunchTab() {
845         final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
846         intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_LAUNCH_TAB, MediaStore.PICK_IMAGES_TAB_ALBUMS);
847 
848         mPickerViewModel.parseValuesFromIntent(intent);
849 
850         // GET_CONTENT doesn't support this option. Launch tab will always default to photos
851         assertThat(mPickerViewModel.getPickerLaunchTab()).isEqualTo(
852                 MediaStore.PICK_IMAGES_TAB_IMAGES);
853     }
854 
855     @Test
testParseValuesFromPickImagesIntent_accentColorsWithUnacceptedBrightness()856     public void testParseValuesFromPickImagesIntent_accentColorsWithUnacceptedBrightness() {
857         // Accent color brightness is less than the accepted brightness
858         long[] accentColors = new long[] {
859                 0xFF000000, // black
860                 0xFFFFFFFF, // white
861                 0xFFFFFFF0  // variant of white
862         };
863 
864         for (long accentColor: accentColors) {
865             final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
866             intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_ACCENT_COLOR, accentColor);
867 
868             mPickerViewModel.parseValuesFromIntent(intent);
869 
870             // Fall back to the android theme
871             assertWithMessage("Input accent color " + accentColor
872                     + " does not fall within accepted luminance range.")
873                     .that(mPickerViewModel.getPickerAccentColorParameters().getPickerAccentColor())
874                     .isEqualTo(-1);
875             assertWithMessage("Custom picker color flag for input color "
876                     + accentColor + " should be false but was true.")
877                     .that(mPickerViewModel.getPickerAccentColorParameters()
878                             .isCustomPickerColorSet()).isFalse();
879         }
880     }
881 
882     @Test
testParseValuesFromGetContentIntent_accentColor()883     public void testParseValuesFromGetContentIntent_accentColor() {
884         long accentColor = 0xFFFF0000;
885         final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
886         intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_ACCENT_COLOR, accentColor);
887 
888         mPickerViewModel.parseValuesFromIntent(intent);
889 
890         // GET_CONTENT doesn't support this option. Accent color parameter object is not
891         // created.
892         assertThat(mPickerViewModel.getPickerAccentColorParameters().getPickerAccentColor())
893                 .isEqualTo(-1);
894         assertThat(mPickerViewModel.getPickerAccentColorParameters().isCustomPickerColorSet())
895                 .isFalse();
896     }
897 
898     @Test
testShouldShowOnlyLocalFeatures()899     public void testShouldShowOnlyLocalFeatures() {
900         mConfigStore.enableCloudMediaFeature();
901 
902         Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
903         intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
904         mPickerViewModel.parseValuesFromIntent(intent);
905         assertThat(mPickerViewModel.isLocalOnly()).isTrue();
906         assertThat(mPickerViewModel.shouldShowOnlyLocalFeatures()).isTrue();
907 
908         intent.removeExtra(Intent.EXTRA_LOCAL_ONLY);
909         mPickerViewModel.parseValuesFromIntent(intent);
910         assertThat(mPickerViewModel.isLocalOnly()).isFalse();
911         assertThat(mPickerViewModel.shouldShowOnlyLocalFeatures()).isFalse();
912 
913         mConfigStore.disableCloudMediaFeature();
914         assertThat(mPickerViewModel.shouldShowOnlyLocalFeatures()).isTrue();
915     }
916 
917     @Test
testRefreshUiNotifications()918     public void testRefreshUiNotifications() throws InterruptedException {
919         final LiveData<RefreshRequest> shouldRefreshUi = mPickerViewModel.refreshUiLiveData();
920         assertFalse(shouldRefreshUi.getValue().shouldRefreshPicker());
921 
922         final ContentResolver contentResolver = sTargetContext.getContentResolver();
923         contentResolver.notifyChange(REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI, null);
924 
925         TimeUnit.MILLISECONDS.sleep(100);
926         assertTrue(shouldRefreshUi.getValue().shouldRefreshPicker());
927 
928         mPickerViewModel.resetAllContentInCurrentProfile(false);
929         assertFalse(shouldRefreshUi.getValue().shouldRefreshPicker());
930     }
931 
932     @Test
testDismissChooseAppBanner()933     public void testDismissChooseAppBanner() {
934         mBannerController.onChangeCloudMediaInfo(CMP_AUTHORITY, CMP_ACCOUNT_NAME);
935         mBannerManager.maybeInitialiseAndSetBannersForCurrentUser();
936 
937         mBannerController.onChangeCloudMediaInfo(
938                 /* cmpAuthority= */ null, /* cmpAccountName= */ null);
939         mBannerManager.maybeInitialiseAndSetBannersForCurrentUser();
940         assertTrue(mBannerController.shouldShowChooseAppBanner());
941         assertTrue(mPickerViewModel.shouldShowChooseAppBannerLiveData().getValue());
942 
943         getInstrumentation().runOnMainSync(() -> mPickerViewModel.onUserDismissedChooseAppBanner());
944         assertFalse(mBannerController.shouldShowChooseAppBanner());
945         assertFalse(mPickerViewModel.shouldShowChooseAppBannerLiveData().getValue());
946 
947         // Assert no change on dismiss when the banner is already hidden
948         getInstrumentation().runOnMainSync(() -> mPickerViewModel.onUserDismissedChooseAppBanner());
949         assertFalse(mBannerController.shouldShowChooseAppBanner());
950         assertFalse(mPickerViewModel.shouldShowChooseAppBannerLiveData().getValue());
951     }
952 
953     @Test
testDismissCloudMediaAvailableBanner()954     public void testDismissCloudMediaAvailableBanner() {
955         mBannerController.onChangeCloudMediaInfo(CMP_AUTHORITY, CMP_ACCOUNT_NAME);
956         mBannerManager.maybeInitialiseAndSetBannersForCurrentUser();
957         assertTrue(mBannerController.shouldShowCloudMediaAvailableBanner());
958         assertTrue(mPickerViewModel.shouldShowCloudMediaAvailableBannerLiveData().getValue());
959 
960         getInstrumentation().runOnMainSync(() ->
961                 mPickerViewModel.onUserDismissedCloudMediaAvailableBanner());
962         assertFalse(mBannerController.shouldShowCloudMediaAvailableBanner());
963         assertFalse(mPickerViewModel.shouldShowCloudMediaAvailableBannerLiveData().getValue());
964 
965         // Assert no change on dismiss when the banner is already hidden
966         getInstrumentation().runOnMainSync(() ->
967                 mPickerViewModel.onUserDismissedCloudMediaAvailableBanner());
968         assertFalse(mBannerController.shouldShowCloudMediaAvailableBanner());
969         assertFalse(mPickerViewModel.shouldShowCloudMediaAvailableBannerLiveData().getValue());
970     }
971 
972     @Test
testDismissAccountUpdatedBanner()973     public void testDismissAccountUpdatedBanner() {
974         mBannerController.onChangeCloudMediaInfo(CMP_AUTHORITY, /* cmpAccountName= */ null);
975         mBannerManager.maybeInitialiseAndSetBannersForCurrentUser();
976 
977         mBannerController.onChangeCloudMediaInfo(CMP_AUTHORITY, CMP_ACCOUNT_NAME);
978         mBannerManager.maybeInitialiseAndSetBannersForCurrentUser();
979         assertTrue(mBannerController.shouldShowAccountUpdatedBanner());
980         assertTrue(mPickerViewModel.shouldShowAccountUpdatedBannerLiveData().getValue());
981 
982         getInstrumentation().runOnMainSync(() ->
983                 mPickerViewModel.onUserDismissedAccountUpdatedBanner());
984         assertFalse(mBannerController.shouldShowAccountUpdatedBanner());
985         assertFalse(mPickerViewModel.shouldShowAccountUpdatedBannerLiveData().getValue());
986 
987         // Assert no change on dismiss when the banner is already hidden
988         getInstrumentation().runOnMainSync(() ->
989                 mPickerViewModel.onUserDismissedAccountUpdatedBanner());
990         assertFalse(mBannerController.shouldShowAccountUpdatedBanner());
991         assertFalse(mPickerViewModel.shouldShowAccountUpdatedBannerLiveData().getValue());
992     }
993 
994     @Test
testDismissChooseAccountBanner()995     public void testDismissChooseAccountBanner() {
996         mBannerController.onChangeCloudMediaInfo(CMP_AUTHORITY, /* cmpAccountName= */ null);
997         mBannerManager.maybeInitialiseAndSetBannersForCurrentUser();
998         assertTrue(mBannerController.shouldShowChooseAccountBanner());
999         assertTrue(mPickerViewModel.shouldShowChooseAccountBannerLiveData().getValue());
1000 
1001         getInstrumentation().runOnMainSync(() ->
1002                 mPickerViewModel.onUserDismissedChooseAccountBanner());
1003         assertFalse(mBannerController.shouldShowChooseAccountBanner());
1004         assertFalse(mPickerViewModel.shouldShowChooseAccountBannerLiveData().getValue());
1005 
1006         // Assert no change on dismiss when the banner is already hidden
1007         getInstrumentation().runOnMainSync(() ->
1008                 mPickerViewModel.onUserDismissedChooseAccountBanner());
1009         assertFalse(mBannerController.shouldShowChooseAccountBanner());
1010         assertFalse(mPickerViewModel.shouldShowChooseAccountBannerLiveData().getValue());
1011     }
1012 
1013     @Test
testGetCloudMediaProviderAuthorityLiveData()1014     public void testGetCloudMediaProviderAuthorityLiveData() {
1015         assertNull(mPickerViewModel.getCloudMediaProviderAuthorityLiveData().getValue());
1016 
1017         mBannerController.onChangeCloudMediaInfo(CMP_AUTHORITY, /* cmpAccountName= */ null);
1018         mBannerManager.maybeInitialiseAndSetBannersForCurrentUser();
1019 
1020         assertEquals(CMP_AUTHORITY,
1021                 mPickerViewModel.getCloudMediaProviderAuthorityLiveData().getValue());
1022     }
1023 
1024     @Test
testGetChooseCloudMediaAccountActivityIntent()1025     public void testGetChooseCloudMediaAccountActivityIntent() {
1026         assertNull(mPickerViewModel.getChooseCloudMediaAccountActivityIntent());
1027 
1028         final Intent testIntent = new Intent();
1029         mBannerController.setChooseCloudMediaAccountActivityIntent(testIntent);
1030         mBannerManager.maybeInitialiseAndSetBannersForCurrentUser();
1031 
1032         assertEquals(testIntent,
1033                 mPickerViewModel.getChooseCloudMediaAccountActivityIntent());
1034     }
1035 
1036     @Test
testMainGridInitRequest()1037     public void testMainGridInitRequest() {
1038         ItemsProvider mockItemsProvider = spy(ItemsProvider.class);
1039         mPickerViewModel.setItemsProvider(mockItemsProvider);
1040 
1041         // Parse values from intent
1042         final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
1043         mPickerViewModel.parseValuesFromIntent(intent);
1044 
1045         // Send an init request
1046         mPickerViewModel.maybeInitPhotoPickerData();
1047 
1048         // Check that the request was sent
1049         DataLoaderThread.waitForIdle();
1050         verify(mockItemsProvider, times(1)).initPhotoPickerData(any(), any(), eq(false), any());
1051 
1052         // Send an init request again
1053         mPickerViewModel.maybeInitPhotoPickerData();
1054 
1055         // Check that init request was NOT sent again
1056         DataLoaderThread.waitForIdle();
1057         verify(mockItemsProvider, times(1)).initPhotoPickerData(any(), any(), eq(false), any());
1058     }
1059 
1060     @SdkSuppress(minSdkVersion = 34, codeName = "UpsideDownCake")
1061     @Test
testMainGridPickerChoiceInitRequest()1062     public void testMainGridPickerChoiceInitRequest() {
1063         ItemsProvider mockItemsProvider = spy(ItemsProvider.class);
1064         mPickerViewModel.setItemsProvider(mockItemsProvider);
1065 
1066         // Parse values from intent
1067         Intent intent =  PhotoPickerBaseTest.getUserSelectImagesForAppIntent();
1068         mPickerViewModel.parseValuesFromIntent(intent);
1069 
1070         // Send an init request
1071         mPickerViewModel.maybeInitPhotoPickerData();
1072 
1073         // Check that a local-only init request was sent
1074         DataLoaderThread.waitForIdle();
1075         verify(mockItemsProvider, times(1)).initPhotoPickerData(any(), any(), eq(true), any());
1076 
1077         // Send an init request again
1078         mPickerViewModel.maybeInitPhotoPickerData();
1079 
1080         // Check that init request was NOT sent again
1081         DataLoaderThread.waitForIdle();
1082         verify(mockItemsProvider, times(1)).initPhotoPickerData(any(), any(), eq(true), any());
1083     }
1084 
1085     @Test
testMainGridInitOnResetRequest()1086     public void testMainGridInitOnResetRequest() {
1087         ItemsProvider mockItemsProvider = spy(ItemsProvider.class);
1088         mPickerViewModel.setItemsProvider(mockItemsProvider);
1089 
1090         // Parse values from intent
1091         Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
1092         mPickerViewModel.parseValuesFromIntent(intent);
1093 
1094         // Send an init request
1095         mPickerViewModel.maybeInitPhotoPickerData();
1096 
1097         // Check that a local-only init request was sent
1098         DataLoaderThread.waitForIdle();
1099         verify(mockItemsProvider, times(1)).initPhotoPickerData(any(), any(), eq(false), any());
1100 
1101         // Send an init request again
1102         mPickerViewModel.resetAllContentInCurrentProfile(true);
1103 
1104         // Check that init request was sent again
1105         DataLoaderThread.waitForIdle();
1106         verify(mockItemsProvider, times(2)).initPhotoPickerData(any(), any(), eq(false), any());
1107     }
1108 }
1109