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;
18 
19 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_FAVORITES;
20 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS;
21 
22 import static com.android.providers.media.PickerProviderMediaGenerator.MediaGenerator;
23 import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.LONG_DEFAULT;
24 import static com.android.providers.media.photopicker.data.PickerDbFacade.QueryFilterBuilder.STRING_DEFAULT;
25 import static com.android.providers.media.photopicker.sync.SyncWorkerTestUtils.initializeTestWorkManager;
26 
27 import static com.google.common.truth.Truth.assertThat;
28 import static com.google.common.truth.Truth.assertWithMessage;
29 
30 import static org.junit.Assert.assertThrows;
31 
32 import android.content.Context;
33 import android.content.Intent;
34 import android.database.Cursor;
35 import android.os.Bundle;
36 import android.provider.CloudMediaProviderContract.AlbumColumns;
37 import android.provider.CloudMediaProviderContract.MediaColumns;
38 import android.provider.MediaStore;
39 import android.util.Pair;
40 
41 import androidx.annotation.NonNull;
42 import androidx.test.InstrumentationRegistry;
43 import androidx.test.runner.AndroidJUnit4;
44 import androidx.work.WorkManager;
45 
46 import com.android.modules.utils.BackgroundThread;
47 import com.android.providers.media.PickerProviderMediaGenerator;
48 import com.android.providers.media.TestConfigStore;
49 import com.android.providers.media.photopicker.data.CloudProviderInfo;
50 import com.android.providers.media.photopicker.data.PickerDatabaseHelper;
51 import com.android.providers.media.photopicker.data.PickerDbFacade;
52 import com.android.providers.media.photopicker.data.PickerSyncRequestExtras;
53 import com.android.providers.media.photopicker.sync.PickerSyncLockManager;
54 import com.android.providers.media.photopicker.sync.PickerSyncManager;
55 import com.android.providers.media.util.ForegroundThread;
56 
57 import org.junit.After;
58 import org.junit.Before;
59 import org.junit.Test;
60 import org.junit.runner.RunWith;
61 
62 import java.io.File;
63 import java.util.concurrent.CountDownLatch;
64 import java.util.concurrent.TimeUnit;
65 
66 @RunWith(AndroidJUnit4.class)
67 public class PickerDataLayerTest {
68     private static final String TAG = "PickerDataLayerTest";
69 
70     private static final String PACKAGE_NAME = "com.android.providers.media.tests";
71     private static final String LOCAL_PROVIDER_AUTHORITY =
72             "com.android.providers.media.photopicker.tests.local";
73     private static final String CLOUD_PRIMARY_PROVIDER_AUTHORITY =
74             "com.android.providers.media.photopicker.tests.cloud_primary";
75     private static final String CLOUD_SECONDARY_PROVIDER_AUTHORITY =
76             "com.android.providers.media.photopicker.tests.cloud_secondary";
77 
78     private final MediaGenerator mLocalMediaGenerator =
79             PickerProviderMediaGenerator.getMediaGenerator(LOCAL_PROVIDER_AUTHORITY);
80     private final MediaGenerator mCloudPrimaryMediaGenerator =
81             PickerProviderMediaGenerator.getMediaGenerator(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
82     private final MediaGenerator mCloudSecondaryMediaGenerator =
83             PickerProviderMediaGenerator.getMediaGenerator(CLOUD_SECONDARY_PROVIDER_AUTHORITY);
84 
85     private static final int DB_VERSION_1 = 1;
86     private static final String DB_NAME = "test_db";
87 
88     private static final String LOCAL_ID_1 = "1";
89     private static final String LOCAL_ID_2 = "2";
90 
91     private static final String CLOUD_ID_1 = "1";
92     private static final String CLOUD_ID_2 = "2";
93 
94     private static final String ALBUM_ID_1 = "1";
95     private static final String ALBUM_ID_2 = "2";
96 
97     private static final String MIME_TYPE_DEFAULT = STRING_DEFAULT;
98     private static final long SIZE_BYTES_DEFAULT = LONG_DEFAULT;
99 
100     private static final Pair<String, String> LOCAL_ONLY_1 = Pair.create(LOCAL_ID_1, null);
101     private static final Pair<String, String> LOCAL_ONLY_2 = Pair.create(LOCAL_ID_2, null);
102     private static final Pair<String, String> CLOUD_ONLY_1 = Pair.create(null, CLOUD_ID_1);
103     private static final Pair<String, String> CLOUD_ONLY_2 = Pair.create(null, CLOUD_ID_2);
104 
105     private static final String COLLECTION_1 = "1";
106 
107     private static final String IMAGE_MIME_TYPE = "image/jpeg";
108     private static final String VIDEO_MIME_TYPE = "video/mp4";
109     private static final long SIZE_BYTES = 50;
110 
111     private Context mContext;
112     private PickerDatabaseHelper mDbHelper;
113     private PickerDbFacade mFacade;
114     private PickerDataLayer mDataLayer;
115     private PickerSyncController mController;
116     private TestConfigStore mConfigStore;
117 
118     @Before
setUp()119     public void setUp() {
120         mLocalMediaGenerator.resetAll();
121         mCloudPrimaryMediaGenerator.resetAll();
122         mCloudSecondaryMediaGenerator.resetAll();
123 
124         mLocalMediaGenerator.setMediaCollectionId(COLLECTION_1);
125         mCloudPrimaryMediaGenerator.setMediaCollectionId(COLLECTION_1);
126         mCloudSecondaryMediaGenerator.setMediaCollectionId(COLLECTION_1);
127 
128         mContext = InstrumentationRegistry.getTargetContext();
129 
130         // Delete db so it's recreated on next access and previous test state is cleared
131         final File dbPath = mContext.getDatabasePath(DB_NAME);
132         dbPath.delete();
133 
134         final PickerSyncLockManager lockManager = new PickerSyncLockManager();
135 
136         mDbHelper = new PickerDatabaseHelper(mContext, DB_NAME, DB_VERSION_1);
137         mFacade = new PickerDbFacade(mContext, lockManager, LOCAL_PROVIDER_AUTHORITY, mDbHelper);
138 
139         mConfigStore = new TestConfigStore();
140         mConfigStore.enableCloudMediaFeatureAndSetAllowedCloudProviderPackages(PACKAGE_NAME);
141 
142         mController = PickerSyncController.initialize(
143                 mContext, mFacade, mConfigStore, lockManager, LOCAL_PROVIDER_AUTHORITY);
144 
145         initializeTestWorkManager(mContext);
146         final WorkManager workManager = WorkManager.getInstance(mContext);
147         final PickerSyncManager syncManager = new PickerSyncManager(
148                 workManager, mContext, mConfigStore, /* schedulePeriodicSyncs */ false);
149         mDataLayer = new PickerDataLayer(mContext, mFacade, mController, mConfigStore, syncManager);
150 
151         // Set cloud provider to null to discard
152         mFacade.setCloudProvider(null);
153     }
154 
155     @After
tearDown()156     public void tearDown() {
157         if (mFacade != null) {
158             mFacade.setCloudProvider(null);
159         }
160     }
161 
162     @Test
testFetchMediaNoFilter()163     public void testFetchMediaNoFilter() {
164         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
165 
166         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
167         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1);
168 
169         final PickerSyncRequestExtras syncRequestExtras = buildDefaultSyncRequestExtras();
170         mDataLayer.initMediaData(syncRequestExtras);
171         try (Cursor cr = mDataLayer.fetchAllMedia(buildDefaultQueryArgs())) {
172             assertThat(cr.getCount()).isEqualTo(2);
173 
174             assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
175             assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
176         }
177     }
178 
179     @Test
testFetchMediaFavorites()180     public void testFetchMediaFavorites() {
181         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
182 
183         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1, /* albumId */ null, VIDEO_MIME_TYPE,
184                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ true);
185         addMedia(mLocalMediaGenerator, LOCAL_ONLY_2, /* albumId */ null, IMAGE_MIME_TYPE,
186                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
187         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1, /* albumId */ null, IMAGE_MIME_TYPE,
188                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES - 1,
189                 /* isFavorite */ true);
190         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_2, /* albumId */ null, IMAGE_MIME_TYPE,
191                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
192 
193         final Bundle defaultQueryArgs = buildDefaultQueryArgs();
194         final PickerSyncRequestExtras syncRequestExtras = buildDefaultSyncRequestExtras();
195         mDataLayer.initMediaData(syncRequestExtras);
196 
197         try (Cursor cr = mDataLayer.fetchAllMedia(defaultQueryArgs)) {
198             assertThat(cr.getCount()).isEqualTo(4);
199         }
200 
201         final Bundle favoriteQueryArgs = buildQueryArgs(ALBUM_ID_FAVORITES,
202                 LOCAL_PROVIDER_AUTHORITY, MIME_TYPE_DEFAULT, SIZE_BYTES_DEFAULT);
203 
204         try (Cursor cr = mDataLayer.fetchAllMedia(favoriteQueryArgs)) {
205             assertThat(cr.getCount()).isEqualTo(2);
206 
207             assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
208             assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
209         }
210     }
211 
212     @Test
testFetchMediaFavoritesMimeTypeFilter()213     public void testFetchMediaFavoritesMimeTypeFilter() {
214         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
215 
216         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1, /* albumId */ null, VIDEO_MIME_TYPE,
217                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ true);
218         addMedia(mLocalMediaGenerator, LOCAL_ONLY_2, /* albumId */ null, IMAGE_MIME_TYPE,
219                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
220         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1, /* albumId */ null, IMAGE_MIME_TYPE,
221                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES - 1,
222                 /* isFavorite */ true);
223         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_2, /* albumId */ null, IMAGE_MIME_TYPE,
224                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
225 
226         final Bundle defaultQueryArgs = buildDefaultQueryArgs();
227         final PickerSyncRequestExtras syncRequestExtras = buildDefaultSyncRequestExtras();
228         mDataLayer.initMediaData(syncRequestExtras);
229 
230         try (Cursor cr = mDataLayer.fetchAllMedia(defaultQueryArgs)) {
231             assertThat(cr.getCount()).isEqualTo(4);
232         }
233 
234         final Bundle favoriteMimeTypeQueryArgs = buildQueryArgs(ALBUM_ID_FAVORITES,
235                 LOCAL_PROVIDER_AUTHORITY, VIDEO_MIME_TYPE, SIZE_BYTES_DEFAULT);
236 
237         try (Cursor cr = mDataLayer.fetchAllMedia(favoriteMimeTypeQueryArgs)) {
238             assertThat(cr.getCount()).isEqualTo(1);
239 
240             assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
241         }
242     }
243 
244     @Test
testFetchMediaFavoritesSizeFilter()245     public void testFetchMediaFavoritesSizeFilter() {
246         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
247 
248         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1, /* albumId */ null, VIDEO_MIME_TYPE,
249                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ true);
250         addMedia(mLocalMediaGenerator, LOCAL_ONLY_2, /* albumId */ null, IMAGE_MIME_TYPE,
251                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
252         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1, /* albumId */ null, IMAGE_MIME_TYPE,
253                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES - 1,
254                 /* isFavorite */ true);
255         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_2, /* albumId */ null, IMAGE_MIME_TYPE,
256                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
257 
258         final Bundle defaultQueryArgs = buildDefaultQueryArgs();
259         final PickerSyncRequestExtras syncRequestExtras = buildDefaultSyncRequestExtras();
260         mDataLayer.initMediaData(syncRequestExtras);
261 
262         try (Cursor cr = mDataLayer.fetchAllMedia(defaultQueryArgs)) {
263             assertThat(cr.getCount()).isEqualTo(4);
264         }
265 
266         final Bundle favoriteSizeQueryArgs = buildQueryArgs(ALBUM_ID_FAVORITES,
267                 LOCAL_PROVIDER_AUTHORITY, MIME_TYPE_DEFAULT, SIZE_BYTES - 1);
268 
269         try (Cursor cr = mDataLayer.fetchAllMedia(favoriteSizeQueryArgs)) {
270             assertThat(cr.getCount()).isEqualTo(1);
271 
272             assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
273         }
274     }
275 
276     @Test
testFetchMediaFavoritesMimeTypeAndSizeFilter()277     public void testFetchMediaFavoritesMimeTypeAndSizeFilter() {
278         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
279 
280         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1, /* albumId */ null, VIDEO_MIME_TYPE,
281                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ true);
282         addMedia(mLocalMediaGenerator, LOCAL_ONLY_2, /* albumId */ null, IMAGE_MIME_TYPE,
283                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
284         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1, /* albumId */ null, IMAGE_MIME_TYPE,
285                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES - 1,
286                 /* isFavorite */ true);
287         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_2, /* albumId */ null, IMAGE_MIME_TYPE,
288                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
289 
290         final Bundle defaultQueryArgs = buildDefaultQueryArgs();
291         final PickerSyncRequestExtras syncRequestExtras = buildDefaultSyncRequestExtras();
292         mDataLayer.initMediaData(syncRequestExtras);
293 
294         try (Cursor cr = mDataLayer.fetchAllMedia(defaultQueryArgs)) {
295             assertThat(cr.getCount()).isEqualTo(4);
296         }
297 
298         final Bundle favoriteSizeAndMimeTypeQueryArgs = buildQueryArgs(ALBUM_ID_FAVORITES,
299                 LOCAL_PROVIDER_AUTHORITY, VIDEO_MIME_TYPE, SIZE_BYTES - 1);
300 
301         try (Cursor cr = mDataLayer.fetchAllMedia(favoriteSizeAndMimeTypeQueryArgs)) {
302             assertThat(cr.getCount()).isEqualTo(0);
303         }
304     }
305 
306     @Test
testFetchMediaMimeTypeFilter()307     public void testFetchMediaMimeTypeFilter() {
308         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
309 
310         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1, /* albumId */ null, IMAGE_MIME_TYPE,
311                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
312         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1, /* albumId */ null, VIDEO_MIME_TYPE,
313                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
314 
315         final Bundle queryArgs = buildQueryArgs(IMAGE_MIME_TYPE, SIZE_BYTES_DEFAULT);
316         final PickerSyncRequestExtras syncRequestExtras = buildDefaultSyncRequestExtras();
317         mDataLayer.initMediaData(syncRequestExtras);
318 
319         try (Cursor cr = mDataLayer.fetchAllMedia(queryArgs)) {
320             assertThat(cr.getCount()).isEqualTo(1);
321 
322             assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
323         }
324     }
325 
326     @Test
testFetchMediaSizeFilter()327     public void testFetchMediaSizeFilter() {
328         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
329 
330         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1, /* albumId */ null, IMAGE_MIME_TYPE,
331                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES - 1,
332                 /* isFavorite */ false);
333         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1, /* albumId */ null, IMAGE_MIME_TYPE,
334                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
335 
336         final Bundle queryArgs = buildQueryArgs(IMAGE_MIME_TYPE, SIZE_BYTES - 1);
337         final PickerSyncRequestExtras syncRequestExtras = buildDefaultSyncRequestExtras();
338         mDataLayer.initMediaData(syncRequestExtras);
339 
340         try (Cursor cr = mDataLayer.fetchAllMedia(queryArgs)) {
341             assertThat(cr.getCount()).isEqualTo(1);
342 
343             assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
344         }
345     }
346 
347     @Test
testFetchMediaMimeTypeAndSizeFilter()348     public void testFetchMediaMimeTypeAndSizeFilter() {
349         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
350 
351         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1, /* albumId */ null, IMAGE_MIME_TYPE,
352                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES - 1,
353                 /* isFavorite */ false);
354         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1, /* albumId */ null, VIDEO_MIME_TYPE,
355                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES - 1,
356                 /* isFavorite */ false);
357         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_2, /* albumId */ null, VIDEO_MIME_TYPE,
358                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
359 
360         final Bundle queryArgs = buildQueryArgs(VIDEO_MIME_TYPE, SIZE_BYTES - 1);
361         final PickerSyncRequestExtras syncRequestExtras = buildDefaultSyncRequestExtras();
362         mDataLayer.initMediaData(syncRequestExtras);
363 
364         try (Cursor cr = mDataLayer.fetchAllMedia(queryArgs)) {
365             assertThat(cr.getCount()).isEqualTo(1);
366 
367             assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
368         }
369     }
370 
371     @Test
testFetchMediaLocalOnly()372     public void testFetchMediaLocalOnly() {
373         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
374 
375         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1);
376         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1);
377 
378         Bundle queryArgs = buildDefaultQueryArgs();
379         final PickerSyncRequestExtras syncRequestExtras = buildDefaultSyncRequestExtras();
380         mDataLayer.initMediaData(syncRequestExtras);
381 
382         // Verify that we only see local content
383         try (Cursor cr = mDataLayer.fetchLocalMedia(queryArgs)) {
384             assertThat(cr.getCount()).isEqualTo(1);
385 
386             assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
387         }
388 
389         // Verify that we see cloud content
390         try (Cursor cr = mDataLayer.fetchAllMedia(queryArgs)) {
391             assertThat(cr.getCount()).isEqualTo(2);
392 
393             assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
394             assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
395         }
396     }
397 
398     @Test
testFetchAlbumMedia()399     public void testFetchAlbumMedia() {
400         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
401 
402         mLocalMediaGenerator.createAlbum(ALBUM_ID_1);
403         mCloudPrimaryMediaGenerator.createAlbum(ALBUM_ID_2);
404 
405         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1, ALBUM_ID_1, IMAGE_MIME_TYPE,
406                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
407         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1, ALBUM_ID_2, VIDEO_MIME_TYPE,
408                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
409         addMedia(mLocalMediaGenerator, LOCAL_ONLY_2, /* albumId */ null, VIDEO_MIME_TYPE,
410                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ true);
411         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_2, /* albumdId */ null, IMAGE_MIME_TYPE,
412                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ true);
413 
414         final Bundle defaultQueryArgs = buildDefaultQueryArgs();
415 
416         mDataLayer.initMediaData(buildDefaultSyncRequestExtras());
417         try (Cursor cr = mDataLayer.fetchAllAlbums(defaultQueryArgs)) {
418             assertThat(cr.getCount()).isEqualTo(4);
419 
420             // Most recent media item marked as favorite will be the cover of the Favorites album.
421             // In this scenario, Favorites album cover was generated with cloud authority, so the
422             // Favorites album authority should be cloud provider authority. Similarly, the most
423             // recent video was generated with local authority, so the Videos album authority should
424             // be local provider authority.
425             assertAlbumCursor(cr, ALBUM_ID_FAVORITES, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
426             assertAlbumCursor(cr, ALBUM_ID_VIDEOS, LOCAL_PROVIDER_AUTHORITY);
427             assertAlbumCursor(cr, ALBUM_ID_1, LOCAL_PROVIDER_AUTHORITY);
428             assertAlbumCursor(cr, ALBUM_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
429         }
430 
431         try (Cursor cr = mDataLayer.fetchAllMedia(defaultQueryArgs)) {
432             assertThat(cr.getCount()).isEqualTo(4);
433 
434             assertCursor(cr, CLOUD_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
435             assertCursor(cr, LOCAL_ID_2, LOCAL_PROVIDER_AUTHORITY);
436             assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
437             assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
438         }
439 
440         final Bundle localAlbumQueryArgs = buildQueryArgs(ALBUM_ID_1,
441                 LOCAL_PROVIDER_AUTHORITY, MIME_TYPE_DEFAULT, SIZE_BYTES_DEFAULT);
442         mDataLayer.initMediaData(buildSyncRequestExtras(ALBUM_ID_1, LOCAL_PROVIDER_AUTHORITY));
443         try (Cursor cr = mDataLayer.fetchAllMedia(localAlbumQueryArgs)) {
444             assertWithMessage("Local album count").that(cr.getCount()).isEqualTo(1);
445 
446             assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
447         }
448 
449         final Bundle cloudAlbumQueryArgs = buildQueryArgs(ALBUM_ID_2,
450                 CLOUD_PRIMARY_PROVIDER_AUTHORITY, MIME_TYPE_DEFAULT, SIZE_BYTES_DEFAULT);
451         mDataLayer.initMediaData(
452                 buildSyncRequestExtras(ALBUM_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY));
453         try (Cursor cr = mDataLayer.fetchAllMedia(cloudAlbumQueryArgs)) {
454             assertWithMessage("Cloud album count").that(cr.getCount()).isEqualTo(1);
455 
456             assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
457         }
458 
459         final Bundle favoriteAlbumQueryArgs = buildQueryArgs(ALBUM_ID_FAVORITES,
460                 LOCAL_PROVIDER_AUTHORITY, MIME_TYPE_DEFAULT, SIZE_BYTES_DEFAULT);
461         mDataLayer.initMediaData(buildDefaultSyncRequestExtras());
462         try (Cursor cr = mDataLayer.fetchAllMedia(favoriteAlbumQueryArgs)) {
463             assertWithMessage("Favorite album count").that(cr.getCount()).isEqualTo(2);
464 
465             assertCursor(cr, CLOUD_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
466             assertCursor(cr, LOCAL_ID_2, LOCAL_PROVIDER_AUTHORITY);
467         }
468     }
469 
470     @Test
testFetchAlbumMediaMimeTypeFilter()471     public void testFetchAlbumMediaMimeTypeFilter() {
472         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
473 
474         mLocalMediaGenerator.createAlbum(ALBUM_ID_1);
475         mCloudPrimaryMediaGenerator.createAlbum(ALBUM_ID_2);
476 
477         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1, ALBUM_ID_1, IMAGE_MIME_TYPE,
478                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
479         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1, ALBUM_ID_2, VIDEO_MIME_TYPE,
480                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
481         addMedia(mLocalMediaGenerator, LOCAL_ONLY_2, ALBUM_ID_1, VIDEO_MIME_TYPE,
482                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
483         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_2, ALBUM_ID_2, IMAGE_MIME_TYPE,
484                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
485 
486         final Bundle mimeTypeQueryArgs = buildQueryArgs(IMAGE_MIME_TYPE, SIZE_BYTES_DEFAULT);
487         final PickerSyncRequestExtras syncRequestExtras = buildDefaultSyncRequestExtras();
488         mDataLayer.initMediaData(syncRequestExtras);
489 
490         try (Cursor cr = mDataLayer.fetchAllAlbums(mimeTypeQueryArgs)) {
491             assertThat(cr.getCount()).isEqualTo(3);
492 
493             // Favorites album will be always visible
494             assertAlbumCursor(cr, ALBUM_ID_FAVORITES, LOCAL_PROVIDER_AUTHORITY);
495             assertAlbumCursor(cr, ALBUM_ID_1, LOCAL_PROVIDER_AUTHORITY);
496             assertAlbumCursor(cr, ALBUM_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
497         }
498 
499         final Bundle localAlbumAndMimeTypeQueryArgs = buildQueryArgs(ALBUM_ID_1,
500                 LOCAL_PROVIDER_AUTHORITY, IMAGE_MIME_TYPE, SIZE_BYTES_DEFAULT);
501         mDataLayer.initMediaData(buildSyncRequestExtras(ALBUM_ID_1, LOCAL_PROVIDER_AUTHORITY));
502         try (Cursor cr = mDataLayer.fetchAllMedia(localAlbumAndMimeTypeQueryArgs)) {
503             assertWithMessage("Local album count").that(cr.getCount()).isEqualTo(1);
504 
505             assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
506         }
507 
508         final Bundle cloudAlbumAndMimeTypeQueryArgs = buildQueryArgs(ALBUM_ID_2,
509                 CLOUD_PRIMARY_PROVIDER_AUTHORITY, IMAGE_MIME_TYPE, SIZE_BYTES_DEFAULT);
510         mDataLayer.initMediaData(
511                 buildSyncRequestExtras(ALBUM_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY));
512         try (Cursor cr = mDataLayer.fetchAllMedia(cloudAlbumAndMimeTypeQueryArgs)) {
513             assertWithMessage("Cloud album count").that(cr.getCount()).isEqualTo(1);
514 
515             assertCursor(cr, CLOUD_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
516         }
517     }
518 
519     @Test
testFetchAlbumMediaSizeFilter()520     public void testFetchAlbumMediaSizeFilter() {
521         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
522 
523         mLocalMediaGenerator.createAlbum(ALBUM_ID_1);
524         mCloudPrimaryMediaGenerator.createAlbum(ALBUM_ID_2);
525 
526         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1, ALBUM_ID_1, IMAGE_MIME_TYPE,
527                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
528         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1, ALBUM_ID_2, VIDEO_MIME_TYPE,
529                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES - 1,
530                 /* isFavorite */ false);
531         addMedia(mLocalMediaGenerator, LOCAL_ONLY_2, ALBUM_ID_1, VIDEO_MIME_TYPE,
532                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES - 1,
533                 /* isFavorite */ false);
534         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_2, ALBUM_ID_2, IMAGE_MIME_TYPE,
535                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
536 
537         final Bundle sizeQueryArgs = buildQueryArgs(MIME_TYPE_DEFAULT, SIZE_BYTES - 1);
538         final PickerSyncRequestExtras syncRequestExtras = buildDefaultSyncRequestExtras();
539         mDataLayer.initMediaData(syncRequestExtras);
540 
541         try (Cursor cr = mDataLayer.fetchAllAlbums(sizeQueryArgs)) {
542             assertThat(cr.getCount()).isEqualTo(4);
543 
544             // Favorites album will be always visible
545             assertAlbumCursor(cr, ALBUM_ID_FAVORITES, LOCAL_PROVIDER_AUTHORITY);
546             assertAlbumCursor(cr, ALBUM_ID_VIDEOS, LOCAL_PROVIDER_AUTHORITY);
547             assertAlbumCursor(cr, ALBUM_ID_1, LOCAL_PROVIDER_AUTHORITY);
548             assertAlbumCursor(cr, ALBUM_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
549         }
550 
551         final Bundle localAlbumAndSizeQueryArgs = buildQueryArgs(ALBUM_ID_1,
552                 LOCAL_PROVIDER_AUTHORITY, MIME_TYPE_DEFAULT, SIZE_BYTES -1);
553         mDataLayer.initMediaData(buildSyncRequestExtras(ALBUM_ID_1, LOCAL_PROVIDER_AUTHORITY));
554         try (Cursor cr = mDataLayer.fetchAllMedia(localAlbumAndSizeQueryArgs)) {
555             assertWithMessage("Local album count").that(cr.getCount()).isEqualTo(1);
556 
557             assertCursor(cr, LOCAL_ID_2, LOCAL_PROVIDER_AUTHORITY);
558         }
559 
560         final Bundle cloudAlbumAndSizeQueryArgs = buildQueryArgs(ALBUM_ID_2,
561                 CLOUD_PRIMARY_PROVIDER_AUTHORITY, MIME_TYPE_DEFAULT, SIZE_BYTES - 1);
562         mDataLayer.initMediaData(
563                 buildSyncRequestExtras(ALBUM_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY));
564         try (Cursor cr = mDataLayer.fetchAllMedia(cloudAlbumAndSizeQueryArgs)) {
565             assertWithMessage("Cloud album count").that(cr.getCount()).isEqualTo(1);
566 
567             assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
568         }
569     }
570 
571     @Test
testFetchAlbumMediaMimeTypeAndSizeFilter()572     public void testFetchAlbumMediaMimeTypeAndSizeFilter() {
573         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
574 
575         mLocalMediaGenerator.createAlbum(ALBUM_ID_1);
576         mCloudPrimaryMediaGenerator.createAlbum(ALBUM_ID_2);
577 
578         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1, ALBUM_ID_1, VIDEO_MIME_TYPE,
579                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
580         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1, ALBUM_ID_2, VIDEO_MIME_TYPE,
581                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES - 1,
582                 /* isFavorite */ false);
583         addMedia(mLocalMediaGenerator, LOCAL_ONLY_2, ALBUM_ID_1, VIDEO_MIME_TYPE,
584                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES - 1,
585                 /* isFavorite */ false);
586         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_2, ALBUM_ID_2, VIDEO_MIME_TYPE,
587                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
588 
589         final Bundle mimeTypeAndSizeQueryArgs = buildQueryArgs(VIDEO_MIME_TYPE, SIZE_BYTES -1);
590         final PickerSyncRequestExtras syncRequestExtras = buildDefaultSyncRequestExtras();
591         mDataLayer.initMediaData(syncRequestExtras);
592 
593         try (Cursor cr = mDataLayer.fetchAllAlbums(mimeTypeAndSizeQueryArgs)) {
594             assertWithMessage("Merged and Local album count").that(cr.getCount()).isEqualTo(4);
595 
596             // Most recent video will be the cover of the Videos album. In this scenario, Videos
597             // album cover was generated with cloud authority, so the Videos album authority should
598             // be cloud provider authority.
599             // Favorites album will always be displayed.
600             assertAlbumCursor(cr, ALBUM_ID_FAVORITES, LOCAL_PROVIDER_AUTHORITY);
601             assertAlbumCursor(cr, ALBUM_ID_VIDEOS, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
602             assertAlbumCursor(cr, ALBUM_ID_1, LOCAL_PROVIDER_AUTHORITY);
603             assertAlbumCursor(cr, ALBUM_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
604         }
605 
606         final Bundle cloudAlbumAndMimeTypeQueryArgs = buildQueryArgs(ALBUM_ID_2,
607                 CLOUD_PRIMARY_PROVIDER_AUTHORITY, VIDEO_MIME_TYPE, SIZE_BYTES - 1);
608         final PickerSyncRequestExtras cloudSyncRequestExtras =
609                 buildSyncRequestExtras(ALBUM_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
610         mDataLayer.initMediaData(cloudSyncRequestExtras);
611         try (Cursor cr = mDataLayer.fetchAllMedia(cloudAlbumAndMimeTypeQueryArgs)) {
612             assertWithMessage("Cloud album count").that(cr.getCount()).isEqualTo(1);
613 
614             assertCursor(cr, CLOUD_ID_1, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
615         }
616     }
617 
618     @Test
testFetchAlbumMediaLocalOnly()619     public void testFetchAlbumMediaLocalOnly() {
620         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
621 
622         // Create a cloud album and add one cloud only item
623         mCloudPrimaryMediaGenerator.createAlbum(ALBUM_ID_1);
624         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_1, ALBUM_ID_1, VIDEO_MIME_TYPE,
625                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ false);
626 
627         // Add one local only and one cloud only item and mark them favorite
628         addMedia(mLocalMediaGenerator, LOCAL_ONLY_1, /* albumId */ null, VIDEO_MIME_TYPE,
629                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ true);
630         addMedia(mCloudPrimaryMediaGenerator, CLOUD_ONLY_2, /* albumdId */ null, IMAGE_MIME_TYPE,
631                 MediaColumns.STANDARD_MIME_TYPE_EXTENSION_NONE, SIZE_BYTES, /* isFavorite */ true);
632 
633         // Album Info:
634         // Album_ID_1 - Cloud Album - 1 Video File
635         // Videos     - Merged Album - 2 Video Files (1 local + 1 cloud)
636         // Favorites  - Merged Album - 2 files (1 local + 1 cloud)
637 
638         final Bundle defaultQueryArgs = buildDefaultQueryArgs();
639         mDataLayer.initMediaData(buildDefaultSyncRequestExtras());
640         // Verify that we see both local and cloud albums
641         try (Cursor cr = mDataLayer.fetchAllAlbums(defaultQueryArgs)) {
642             // Favorites and Videos merged albums will be always visible
643             assertThat(cr.getCount()).isEqualTo(3);
644         }
645 
646         // Verify that we only see local albums with isLocalOnly=true
647         try (Cursor cr = mDataLayer.fetchLocalAlbums(defaultQueryArgs)) {
648             assertThat(cr.getCount()).isEqualTo(2);
649 
650             cr.moveToNext();
651             assertThat(cr.getString(cr.getColumnIndex(AlbumColumns.ID)))
652                     .isEqualTo(ALBUM_ID_FAVORITES);
653             cr.moveToNext();
654             assertThat(cr.getString(cr.getColumnIndex(AlbumColumns.ID)))
655                     .isEqualTo(ALBUM_ID_VIDEOS);
656         }
657 
658         final Bundle favoriteAlbumQueryArgs = buildQueryArgs(ALBUM_ID_FAVORITES,
659                 LOCAL_PROVIDER_AUTHORITY, MIME_TYPE_DEFAULT, SIZE_BYTES_DEFAULT);
660 
661         try (Cursor cr = mDataLayer.fetchLocalMedia(favoriteAlbumQueryArgs)) {
662             assertWithMessage("Favorite album count").that(cr.getCount()).isEqualTo(1);
663 
664             assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
665         }
666 
667         // Verify that we get cloud content in Album Media when queried with localOnly=false
668         try (Cursor cr = mDataLayer.fetchAllMedia(favoriteAlbumQueryArgs)) {
669             assertWithMessage("Favorite album count").that(cr.getCount()).isEqualTo(2);
670 
671             assertCursor(cr, CLOUD_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
672             assertCursor(cr, LOCAL_ID_1, LOCAL_PROVIDER_AUTHORITY);
673         }
674     }
675 
676     @Test
testFetchCloudAccountInfo()677     public void testFetchCloudAccountInfo() {
678         // Cloud provider is not set so cloud account info is null
679         assertThat(mDataLayer.fetchCloudAccountInfo()).isNull();
680 
681         // Set cloud provider
682         mFacade.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
683 
684         // Still null since cloud provider doesn't return account info yet
685         assertThat(mDataLayer.fetchCloudAccountInfo()).isNull();
686 
687         // Fake cloud provider cloud account info
688         final String expectedName = "bar";
689         final Intent expectedIntent = new Intent("foo");
690         mCloudPrimaryMediaGenerator.setAccountInfo(expectedName, expectedIntent);
691 
692         // Verify account info
693         final PickerDataLayer.AccountInfo info = mDataLayer.fetchCloudAccountInfo();
694         assertThat(info).isNotNull();
695         assertThat(info.accountName).isEqualTo(expectedName);
696         assertThat(info.accountConfigurationIntent).isEqualTo(expectedIntent);
697     }
698 
699     @Test
testInitMediaDataInvalidData()700     public void testInitMediaDataInvalidData() {
701         final Bundle syncExtrasBundle = new Bundle();
702         syncExtrasBundle.putString(MediaStore.EXTRA_ALBUM_ID, "NotMergedAlbum");
703         syncExtrasBundle.putString(MediaStore.EXTRA_ALBUM_AUTHORITY, "NotLocalAuthority");
704         syncExtrasBundle.putBoolean(MediaStore.EXTRA_LOCAL_ONLY, true);
705         final PickerSyncRequestExtras syncExtras =
706                 PickerSyncRequestExtras.fromBundle(syncExtrasBundle);
707 
708         assertThrows(IllegalStateException.class,
709                 () -> mDataLayer.initMediaData(syncExtras));
710     }
711 
712     @Test
testCloudPackageAllowlistListenerRemovesActiveThatIsNowInvalid()713     public void testCloudPackageAllowlistListenerRemovesActiveThatIsNowInvalid() {
714         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
715         assertThat(mController.getCurrentCloudProviderInfo().packageName).isEqualTo(PACKAGE_NAME);
716 
717         // Simulate a DeviceConfig change where the Allowlist is set to empty.
718         mConfigStore.setAllowedCloudProviderPackages(new String[] {});
719 
720 
721         // The listener uses the ForegroundThread to run the listener, so wait for the
722         // ForegroundThread to complete.
723         ForegroundThread.waitForIdle();
724 
725         assertThat(mController.getCurrentCloudProviderInfo()).isEqualTo(CloudProviderInfo.EMPTY);
726     }
727 
728     @Test
testCloudPackageAllowlistListenerDoesNotChangeAllowedProvider()729     public void testCloudPackageAllowlistListenerDoesNotChangeAllowedProvider() {
730         mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
731         assertThat(mController.getCurrentCloudProviderInfo().packageName).isEqualTo(PACKAGE_NAME);
732 
733         // Simulate a DeviceConfig change where the Allowlist adds a new provider, but the current
734         // provider is still permitted.
735         final String newlyAddedProviderPackage = "com.hooli.super.awesome.cloud.provider";
736         mConfigStore.setAllowedCloudProviderPackages(
737                 new String[] {PACKAGE_NAME, newlyAddedProviderPackage});
738 
739         // The listener uses the ForegroundThread to run the listener, so wait for the
740         // ForegroundThread to complete.
741         ForegroundThread.waitForIdle();
742 
743         // Ensure nothing was changed.
744         assertThat(mController.getCurrentCloudProviderInfo().packageName).isEqualTo(PACKAGE_NAME);
745     }
746 
waitForIdle()747     private static void waitForIdle() {
748         final CountDownLatch latch = new CountDownLatch(1);
749         BackgroundThread.getExecutor().execute(() -> {
750             latch.countDown();
751         });
752         try {
753             latch.await(30, TimeUnit.SECONDS);
754         } catch (InterruptedException e) {
755             throw new IllegalStateException(e);
756         }
757 
758     }
759 
buildDefaultQueryArgs()760     private static Bundle buildDefaultQueryArgs() {
761         return buildQueryArgs(MIME_TYPE_DEFAULT, SIZE_BYTES_DEFAULT);
762     }
763 
buildQueryArgs(String mimeType, long sizeBytes)764     private static Bundle buildQueryArgs(String mimeType, long sizeBytes) {
765         final Bundle queryArgs = new Bundle();
766 
767         if (mimeType != null) {
768             queryArgs.putStringArray(MediaStore.QUERY_ARG_MIME_TYPE, new String[]{mimeType});
769         }
770         queryArgs.putLong(MediaStore.QUERY_ARG_SIZE_BYTES, sizeBytes);
771 
772         return queryArgs;
773     }
774 
buildQueryArgs(String albumId, String albumAuthority, String mimeType, long sizeBytes)775     private static Bundle buildQueryArgs(String albumId, String albumAuthority, String mimeType,
776             long sizeBytes) {
777         final Bundle queryArgs = buildQueryArgs(mimeType, sizeBytes);
778 
779         queryArgs.putString(MediaStore.QUERY_ARG_ALBUM_ID, albumId);
780         queryArgs.putString(MediaStore.QUERY_ARG_ALBUM_AUTHORITY, albumAuthority);
781 
782         return queryArgs;
783     }
784 
785     @NonNull
buildDefaultSyncRequestExtras()786     private static PickerSyncRequestExtras buildDefaultSyncRequestExtras() {
787         return PickerSyncRequestExtras.fromBundle(buildDefaultSyncRequestBundle());
788     }
789 
790     @NonNull
buildSyncRequestExtras(@onNull String albumId, @NonNull String albumAuthority)791     private static PickerSyncRequestExtras buildSyncRequestExtras(@NonNull String albumId,
792             @NonNull String albumAuthority) {
793         final Bundle syncRequestExtras = buildDefaultSyncRequestBundle();
794         syncRequestExtras.putString(MediaStore.EXTRA_ALBUM_ID, albumId);
795         syncRequestExtras.putString(MediaStore.EXTRA_ALBUM_AUTHORITY, albumAuthority);
796 
797         return PickerSyncRequestExtras
798                 .fromBundle(syncRequestExtras);
799     }
800 
801     @NonNull
buildDefaultSyncRequestBundle()802     private static Bundle buildDefaultSyncRequestBundle() {
803         final Bundle syncRequestExtras = new Bundle();
804         syncRequestExtras.putBoolean(MediaStore.EXTRA_LOCAL_ONLY, false);
805 
806         return syncRequestExtras;
807     }
808 
addMedia(MediaGenerator generator, Pair<String, String> media)809     private static void addMedia(MediaGenerator generator, Pair<String, String> media) {
810         generator.addMedia(media.first, media.second);
811     }
812 
addMedia(MediaGenerator generator, Pair<String, String> media, String albumId, String mimeType, int standardMimeTypeExtension, long sizeBytes, boolean isFavorite)813     private static void addMedia(MediaGenerator generator, Pair<String, String> media,
814             String albumId, String mimeType, int standardMimeTypeExtension, long sizeBytes,
815             boolean isFavorite) {
816         generator.addMedia(media.first, media.second, albumId, mimeType,
817                 standardMimeTypeExtension, sizeBytes, isFavorite);
818     }
819 
deleteMedia(MediaGenerator generator, Pair<String, String> media)820     private static void deleteMedia(MediaGenerator generator, Pair<String, String> media) {
821         generator.deleteMedia(media.first, media.second);
822     }
823 
queryMedia()824     private Cursor queryMedia() {
825         return mFacade.queryMediaForUi(
826                 new PickerDbFacade.QueryFilterBuilder(1000).build());
827     }
828 
assertEmptyCursor()829     private void assertEmptyCursor() {
830         try (Cursor cr = queryMedia()) {
831             assertThat(cr.getCount()).isEqualTo(0);
832         }
833     }
834 
assertAlbumCursor(Cursor cursor, String id, String expectedAuthority)835     private static void assertAlbumCursor(Cursor cursor, String id, String expectedAuthority) {
836         cursor.moveToNext();
837         assertThat(cursor.getString(cursor.getColumnIndex(AlbumColumns.ID)))
838                 .isEqualTo(id);
839         final int authorityIdx = cursor.getColumnIndex(AlbumColumns.AUTHORITY);
840         String authority = null;
841         if (authorityIdx >= 0) {
842             // Cursor from picker db has authority as a column
843             authority = cursor.getString(authorityIdx);
844         }
845         if (authority == null) {
846             // Cursor from provider directly doesn't have an authority column but will
847             // have the authority set as an extra
848             final Bundle bundle = cursor.getExtras();
849             authority = bundle.getString(MediaStore.EXTRA_CLOUD_PROVIDER);
850         }
851 
852         assertThat(authority).isEqualTo(expectedAuthority);
853     }
854 
assertCursor(Cursor cursor, String id, String expectedAuthority)855     private static void assertCursor(Cursor cursor, String id, String expectedAuthority) {
856         cursor.moveToNext();
857         assertThat(cursor.getString(cursor.getColumnIndex(MediaColumns.ID)))
858                 .isEqualTo(id);
859         assertThat(cursor.getString(cursor.getColumnIndex(MediaColumns.AUTHORITY)))
860                 .isEqualTo(expectedAuthority);
861     }
862 }
863