• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.data;
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.photopicker.util.PickerDbTestUtils.ALBUM_ID;
23 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.CLOUD_ID;
24 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.CLOUD_ID_1;
25 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.CLOUD_ID_2;
26 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.CLOUD_ID_3;
27 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.CLOUD_ID_4;
28 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.CLOUD_PROVIDER;
29 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.DATE_TAKEN_MS;
30 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.GENERATION_MODIFIED;
31 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.GIF_IMAGE_MIME_TYPE;
32 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.IMAGE_MIME_TYPES_QUERY;
33 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.JPEG_IMAGE_MIME_TYPE;
34 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.LOCAL_ID;
35 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.LOCAL_ID_1;
36 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.LOCAL_ID_2;
37 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.LOCAL_ID_3;
38 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.LOCAL_ID_4;
39 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.LOCAL_PROVIDER;
40 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.M4V_VIDEO_MIME_TYPE;
41 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.MP4_VIDEO_MIME_TYPE;
42 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.MPEG_VIDEO_MIME_TYPE;
43 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.PNG_IMAGE_MIME_TYPE;
44 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.SIZE_BYTES;
45 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.STANDARD_MIME_TYPE_EXTENSION;
46 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.TEST_PACKAGE_NAME;
47 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.VIDEO_MIME_TYPES_QUERY;
48 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.WEBM_VIDEO_MIME_TYPE;
49 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertAddAlbumMediaOperation;
50 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertAddMediaOperation;
51 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertAllMediaCursor;
52 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertClearGrantsOperation;
53 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertCloudAlbumCursor;
54 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertCloudMediaCursor;
55 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertGrantsCursor;
56 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertInsertGrantsOperation;
57 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertMediaStoreCursor;
58 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertRemoveMediaOperation;
59 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertResetAlbumMediaOperation;
60 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertResetMediaOperation;
61 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.assertWriteOperation;
62 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getAlbumMediaCursor;
63 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getCloudMediaCursor;
64 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getDeletedMediaCursor;
65 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getLocalMediaCursor;
66 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getMediaCursor;
67 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.getMediaGrantsCursor;
68 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.queryAlbumMedia;
69 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.queryGrants;
70 import static com.android.providers.media.photopicker.util.PickerDbTestUtils.queryMediaAll;
71 
72 import static com.google.common.truth.Truth.assertWithMessage;
73 
74 import static org.junit.Assert.assertThrows;
75 import static org.mockito.Mockito.doReturn;
76 import static org.mockito.MockitoAnnotations.initMocks;
77 
78 import android.content.ContentValues;
79 import android.content.Context;
80 import android.database.Cursor;
81 import android.database.sqlite.SQLiteQueryBuilder;
82 import android.os.UserHandle;
83 import android.provider.CloudMediaProviderContract.MediaColumns;
84 import android.provider.Column;
85 import android.provider.ExportedSince;
86 import android.provider.MediaStore.PickerMediaColumns;
87 
88 import androidx.test.ext.junit.runners.AndroidJUnit4;
89 import androidx.test.platform.app.InstrumentationRegistry;
90 
91 import com.android.providers.media.MediaGrants;
92 import com.android.providers.media.PickerUriResolver;
93 import com.android.providers.media.ProjectionHelper;
94 import com.android.providers.media.photopicker.sync.PickerSyncLockManager;
95 import com.android.providers.media.photopicker.sync.SyncTracker;
96 import com.android.providers.media.photopicker.sync.SyncTrackerRegistry;
97 
98 import org.junit.After;
99 import org.junit.Before;
100 import org.junit.Test;
101 import org.junit.runner.RunWith;
102 import org.mockito.Mock;
103 
104 import java.io.File;
105 import java.util.Collections;
106 import java.util.List;
107 import java.util.concurrent.CompletableFuture;
108 
109 @RunWith(AndroidJUnit4.class)
110 public class PickerDbFacadeTest {
111     private PickerDbFacade mFacade;
112     private Context mContext;
113     private ProjectionHelper mProjectionHelper;
114 
115     @Mock
116     private SyncTracker mMockLocalSyncTracker;
117     @Mock
118     private SyncTracker mMockCloudSyncTracker;
119 
120     @Before
setUp()121     public void setUp() {
122         initMocks(this);
123         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
124         File dbPath = mContext.getDatabasePath(PickerDatabaseHelper.PICKER_DATABASE_NAME);
125         dbPath.delete();
126         mFacade = new PickerDbFacade(mContext, new PickerSyncLockManager(), LOCAL_PROVIDER);
127         mFacade.setCloudProvider(CLOUD_PROVIDER);
128         mProjectionHelper = new ProjectionHelper(Column.class, ExportedSince.class);
129 
130 
131         // Inject mock trackers
132         SyncTrackerRegistry.setLocalSyncTracker(mMockLocalSyncTracker);
133         SyncTrackerRegistry.setCloudSyncTracker(mMockCloudSyncTracker);
134     }
135 
136     @After
tearDown()137     public void tearDown() {
138         if (mFacade != null) {
139             mFacade.setCloudProvider(null);
140         }
141 
142         // Reset mock trackers
143         SyncTrackerRegistry.setLocalSyncTracker(new SyncTracker());
144         SyncTrackerRegistry.setCloudSyncTracker(new SyncTracker());
145     }
146 
147     @Test
testAddLocalOnlyMedia()148     public void testAddLocalOnlyMedia() throws Exception {
149         Cursor cursor1 = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS + 1);
150         Cursor cursor2 = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS + 2);
151 
152         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor1, 1);
153 
154         try (Cursor cr = queryMediaAll(mFacade)) {
155             assertWithMessage(
156                     "Unexpected number of media after addMediaOperation with cursor1 "
157                             + "on LOCAL_PROVIDER.")
158                     .that(cr.getCount()).isEqualTo(1);
159             cr.moveToFirst();
160             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS + 1);
161         }
162 
163         // Test updating the same row
164         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor2, 1);
165 
166         try (Cursor cr = queryMediaAll(mFacade)) {
167             assertWithMessage(
168                     "Unexpected number of media after trying to update the same row with cursor2 "
169                             + "on LOCAL_PROVIDER.")
170                     .that(cr.getCount()).isEqualTo(1);
171             cr.moveToFirst();
172             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS + 2);
173         }
174     }
175 
176     @Test
testAddCloudPlusLocal()177     public void testAddCloudPlusLocal() throws Exception {
178         Cursor cursor = getCloudMediaCursor(CLOUD_ID, LOCAL_ID, DATE_TAKEN_MS);
179 
180         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor, 1);
181 
182         try (Cursor cr = queryMediaAll(mFacade)) {
183             assertWithMessage(
184                     "Unexpected number of media after addMediaOperation on CLOUD_PROVIDER.")
185                     .that(cr.getCount()).isEqualTo(1);
186             cr.moveToFirst();
187             assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS);
188         }
189     }
190 
191     @Test
testAddCloudOnly()192     public void testAddCloudOnly() throws Exception {
193         Cursor cursor1 = getCloudMediaCursor(CLOUD_ID, null, DATE_TAKEN_MS + 1);
194         Cursor cursor2 = getCloudMediaCursor(CLOUD_ID, null, DATE_TAKEN_MS + 2);
195 
196         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor1, 1);
197 
198         try (Cursor cr = queryMediaAll(mFacade)) {
199             assertWithMessage(
200                     "Unexpected number of media after addMediaOperation with cursor1 on "
201                             + "CLOUD_PROVIDER.")
202                     .that(cr.getCount()).isEqualTo(1);
203             cr.moveToFirst();
204             assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS + 1);
205         }
206 
207         // Test updating the same row
208         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor2, 1);
209 
210         try (Cursor cr = queryMediaAll(mFacade)) {
211             assertWithMessage(
212                     "Unexpected number of media after trying to update the same row with cursor2 "
213                             + "on CLOUD_PROVIDER.")
214                     .that(cr.getCount()).isEqualTo(1);
215             cr.moveToFirst();
216             assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS + 2);
217         }
218     }
219 
220     @Test
testAddLocalAndCloud_Dedupe()221     public void testAddLocalAndCloud_Dedupe() throws Exception {
222         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
223         Cursor cloudCursor = getCloudMediaCursor(CLOUD_ID, LOCAL_ID, DATE_TAKEN_MS + 1);
224 
225         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
226         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor, 1);
227 
228         try (Cursor cr = queryMediaAll(mFacade)) {
229             assertWithMessage(
230                     "Unexpected number of media after addMediaOperation with:\nlocalCursor having "
231                             + "localId = " + LOCAL_ID + ", followed by\ncloudCursor having "
232                             + "localId = " + LOCAL_ID + ", cloudId = " + CLOUD_ID)
233                     .that(cr.getCount()).isEqualTo(1);
234             cr.moveToFirst();
235             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS);
236         }
237     }
238 
239     @Test
testAddCloudAndLocal_Dedupe()240     public void testAddCloudAndLocal_Dedupe() throws Exception {
241         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS + 1);
242         Cursor cloudCursor = getCloudMediaCursor(CLOUD_ID, LOCAL_ID, DATE_TAKEN_MS);
243 
244         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor, 1);
245         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
246 
247         try (Cursor cr = queryMediaAll(mFacade)) {
248             assertWithMessage(
249                     "Unexpected number of media after addMediaOperation with:\ncloudCursor having "
250                             + "localId = " + LOCAL_ID + ", cloudId = " + CLOUD_ID + ", followed by"
251                             + "\ncloudCursor having localId = " + LOCAL_ID)
252                     .that(cr.getCount()).isEqualTo(1);
253             cr.moveToFirst();
254             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS + 1);
255         }
256     }
257 
258     @Test
testMediaSortOrder()259     public void testMediaSortOrder() {
260         final Cursor cursor1 = getLocalMediaCursor(LOCAL_ID_1, DATE_TAKEN_MS);
261         final Cursor cursor2 = getCloudMediaCursor(CLOUD_ID_1, null, DATE_TAKEN_MS);
262         final Cursor cursor3 = getLocalMediaCursor(LOCAL_ID_2, DATE_TAKEN_MS + 1);
263 
264         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor1, 1);
265         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor2, 1);
266         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor3, 1);
267 
268         try (Cursor cr = queryMediaAll(mFacade)) {
269             assertWithMessage(
270                     "Unexpected number of media on queryMediaAll() after adding 2 "
271                             + "localMediaCursor and 1 cloudMediaCursor to "
272                             + LOCAL_PROVIDER + " and " + CLOUD_PROVIDER + " respectively.")
273                     .that(cr.getCount()).isEqualTo(/* expected= */ 3);
274 
275             cr.moveToFirst();
276             // Latest items should show up first.
277             assertCloudMediaCursor(cr, LOCAL_ID_2, DATE_TAKEN_MS + 1);
278 
279             cr.moveToNext();
280             // If the date taken is the same for 2 or more items, they should be sorted in the order
281             // of their insertion in the database with the latest row inserted first.
282             assertCloudMediaCursor(cr, CLOUD_ID_1, DATE_TAKEN_MS);
283 
284             cr.moveToNext();
285             assertCloudMediaCursor(cr, LOCAL_ID_1, DATE_TAKEN_MS);
286         }
287     }
288 
289     @Test
testAddLocalAlbumMedia()290     public void testAddLocalAlbumMedia() {
291         Cursor cursor1 = getAlbumMediaCursor(LOCAL_ID, /* cloud id */ null, DATE_TAKEN_MS + 1);
292         Cursor cursor2 = getAlbumMediaCursor(LOCAL_ID, /* cloud id */ null, DATE_TAKEN_MS + 2);
293 
294         assertAddAlbumMediaOperation(mFacade, LOCAL_PROVIDER, cursor1, 1, ALBUM_ID);
295 
296         try (Cursor cr = queryAlbumMedia(mFacade, ALBUM_ID, true)) {
297             assertWithMessage(
298                     "Unexpected number of albumMedia after adding albumMediaCursor having localId"
299                             + " = "
300                             + LOCAL_ID + " cloudId = " + null + " to " + LOCAL_PROVIDER)
301                     .that(cr.getCount()).isEqualTo(1);
302             cr.moveToFirst();
303             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS + 1);
304         }
305 
306         // Test updating the same row. We always do a full sync for album media files.
307         assertResetAlbumMediaOperation(mFacade, LOCAL_PROVIDER, 1, ALBUM_ID);
308         assertAddAlbumMediaOperation(mFacade, LOCAL_PROVIDER, cursor2, 1, ALBUM_ID);
309 
310         try (Cursor cr = queryAlbumMedia(mFacade, ALBUM_ID, true)) {
311             assertWithMessage(
312                     "Unexpected number of albumMedia after resetting and updating the same row "
313                             + "with albumMediaCursor having localId = "
314                             + LOCAL_ID + " cloudId = " + null)
315                     .that(cr.getCount()).isEqualTo(1);
316             cr.moveToFirst();
317             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS + 2);
318         }
319     }
320 
321     @Test
testAddCloudAlbumMedia()322     public void testAddCloudAlbumMedia() {
323         Cursor cursor1 = getAlbumMediaCursor(/* local id */ null, CLOUD_ID, DATE_TAKEN_MS + 1);
324         Cursor cursor2 = getAlbumMediaCursor(/* local id */ null, CLOUD_ID, DATE_TAKEN_MS + 2);
325 
326         assertAddAlbumMediaOperation(mFacade, CLOUD_PROVIDER, cursor1, 1, ALBUM_ID);
327 
328         try (Cursor cr = queryAlbumMedia(mFacade, ALBUM_ID, false)) {
329             assertWithMessage(
330                     "Unexpected number of albumMedia after adding albumMediaCursor having localId"
331                             + " = "
332                             + null + " cloudId = " + CLOUD_ID + " to " + CLOUD_PROVIDER)
333                     .that(cr.getCount()).isEqualTo(1);
334             cr.moveToFirst();
335             assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS + 1);
336         }
337 
338         // Test updating the same row. We always do a full sync for album media files.
339         assertResetAlbumMediaOperation(mFacade, CLOUD_PROVIDER, 1, ALBUM_ID);
340         assertAddAlbumMediaOperation(mFacade, CLOUD_PROVIDER, cursor2, 1, ALBUM_ID);
341 
342         try (Cursor cr = queryAlbumMedia(mFacade, ALBUM_ID, false)) {
343             assertWithMessage(
344                     "Unexpected number of albumMedia after resetting and updating the same row "
345                     + "with albumMediaCursor having localId = "
346                             + null + " cloudId = " + CLOUD_PROVIDER)
347                     .that(cr.getCount()).isEqualTo(1);
348             cr.moveToFirst();
349             assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS + 2);
350         }
351     }
352 
353     @Test
testAddAndClearGrants()354     public void testAddAndClearGrants() {
355         Cursor cursor1 = getMediaGrantsCursor(LOCAL_ID);
356 
357         // insert a grants.
358         assertInsertGrantsOperation(mFacade, cursor1, 1);
359         // verify the grants is present in the database.
360         try (Cursor cr = queryGrants(mFacade)) {
361             assertWithMessage(
362                     "Unexpected number of grants ")
363                     .that(cr.getCount()).isEqualTo(1);
364             cr.moveToFirst();
365             assertGrantsCursor(cr, LOCAL_ID);
366         }
367 
368         // clear all grants.
369         assertClearGrantsOperation(mFacade, 1, new String[]{TEST_PACKAGE_NAME},
370                 UserHandle.myUserId());
371         // verify that the grants have been cleared.
372         try (Cursor cr = queryGrants(mFacade)) {
373             assertWithMessage(
374                     "Unexpected number of grants ")
375                     .that(cr.getCount()).isEqualTo(0);
376         }
377     }
378 
379     @Test
testAddWhereClausesForMediaGrantsTable()380     public void testAddWhereClausesForMediaGrantsTable() {
381         // set up
382         SQLiteQueryBuilder sqb = new SQLiteQueryBuilder();
383         int testUserId = 1;
384         String[] testPackageNames = {"com.test.example"};
385 
386         // adding where clause
387         PickerDbFacade.addWhereClausesForMediaGrantsTable(sqb, testUserId, testPackageNames);
388 
389         // verify where clauses have been added to the query.
390         String resultQuery = sqb.buildQuery(null, null, null, null, null, null);
391 
392         assertWithMessage("Query should contain clause for userId.").that(
393                 resultQuery.contains(String.format("%s = %d", MediaGrants.PACKAGE_USER_ID_COLUMN,
394                         testUserId))).isEqualTo(true);
395         assertWithMessage("Query should contain clause for packageNames.")
396                 .that(resultQuery.contains(String.format("%s IN (\"%s\")",
397                         MediaGrants.OWNER_PACKAGE_NAME_COLUMN,
398                         testPackageNames[0]))).isEqualTo(
399                         true);
400     }
401 
402     @Test
testAddCloudAlbumMediaWhileCloudSyncIsRunning()403     public void testAddCloudAlbumMediaWhileCloudSyncIsRunning() {
404 
405 
406         doReturn(Collections.singletonList(new CompletableFuture<>()))
407                 .when(mMockCloudSyncTracker)
408                 .pendingSyncFutures();
409 
410         Cursor cursor1 = getAlbumMediaCursor(/* local id */ null, CLOUD_ID, DATE_TAKEN_MS + 1);
411 
412         assertAddAlbumMediaOperation(mFacade, CLOUD_PROVIDER, cursor1, 1, ALBUM_ID);
413 
414         try (Cursor cr = queryAlbumMedia(mFacade, ALBUM_ID, false)) {
415             assertWithMessage(
416                     "Unexpected number of albumMedia after adding albumMediaCursor having localId"
417                     + " = "
418                             + null + " cloudId = " + CLOUD_ID + " to " + CLOUD_PROVIDER)
419                     .that(cr.getCount()).isEqualTo(1);
420             cr.moveToFirst();
421             assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS + 1);
422         }
423 
424         // These files should also be in the media table since we're pretending that
425         // we have a cloud sync running.
426         try (Cursor cr = queryMediaAll(mFacade)) {
427             assertWithMessage(
428                     "Unexpected number of media on querying all media with cloud sync running.")
429                     .that(cr.getCount()).isEqualTo(1);
430             cr.moveToFirst();
431             assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS + 1);
432         }
433     }
434 
435     @Test
testAddCloudAlbumMediaAvailableOnDevice()436     public void testAddCloudAlbumMediaAvailableOnDevice() {
437         // Add local row for a media item in media table.
438         final Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
439         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
440 
441         // Attempt to insert a media item available locally and on cloud in album_media table.
442         final Cursor cloudCursor =
443                 getAlbumMediaCursor(LOCAL_ID, CLOUD_ID, DATE_TAKEN_MS + 1);
444         assertAddAlbumMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor, 1, ALBUM_ID);
445 
446         // Assert that preference was given to the local media item over cloud media item at the
447         // time of insertion in album_media table.
448         try (Cursor albumCursor = queryAlbumMedia(mFacade, ALBUM_ID, false)) {
449             assertWithMessage(
450                     "Unexpected number of albumMedia on querying " + ALBUM_ID)
451                     .that(albumCursor.getCount()).isEqualTo(1);
452             albumCursor.moveToFirst();
453             assertCloudMediaCursor(albumCursor, LOCAL_ID, DATE_TAKEN_MS);
454         }
455     }
456 
457     @Test
testAddCloudAlbumMediaDeletedFromDevice()458     public void testAddCloudAlbumMediaDeletedFromDevice() {
459         // Attempt to insert a media item deleted from device and available on cloud in the
460         // album_media table.
461         final Cursor cloudCursor =
462                 getAlbumMediaCursor(LOCAL_ID, CLOUD_ID, DATE_TAKEN_MS);
463         assertAddAlbumMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor, 1, ALBUM_ID);
464 
465         // Assert that cloud media metadata was inserted in the database as local_id points to a
466         // deleted item.
467         try (Cursor albumCursor = queryAlbumMedia(mFacade, ALBUM_ID, false)) {
468             assertWithMessage(
469                     "Unexpected number of albumMedia on querying " + ALBUM_ID)
470                     .that(albumCursor.getCount()).isEqualTo(1);
471             albumCursor.moveToFirst();
472             assertCloudMediaCursor(albumCursor, CLOUD_ID, DATE_TAKEN_MS);
473         }
474     }
475 
476     @Test
testAlbumMediaSortOrder()477     public void testAlbumMediaSortOrder() {
478         final Cursor cursor1 = getAlbumMediaCursor(null, CLOUD_ID_1, DATE_TAKEN_MS);
479         final Cursor cursor2 = getAlbumMediaCursor(LOCAL_ID_1, null, DATE_TAKEN_MS);
480         final Cursor cursor3 = getAlbumMediaCursor(null, CLOUD_ID_2, DATE_TAKEN_MS + 1);
481 
482         assertAddAlbumMediaOperation(mFacade, CLOUD_PROVIDER, cursor1, 1, ALBUM_ID);
483         assertAddAlbumMediaOperation(mFacade, LOCAL_PROVIDER, cursor2, 1, ALBUM_ID);
484         assertAddAlbumMediaOperation(mFacade, CLOUD_PROVIDER, cursor3, 1, ALBUM_ID);
485 
486         try (Cursor cr = queryAlbumMedia(mFacade, ALBUM_ID, false)) {
487             assertWithMessage(
488                     "Unexpected number of media on queryMediaAll() after adding 2 "
489                             + "cloudAlbumMediaCursor and 1 localAlbumMediaCursor to "
490                             + CLOUD_PROVIDER + " and " + LOCAL_PROVIDER + " respectively.")
491                     .that(cr.getCount()).isEqualTo(/* expected= */ 3);
492 
493             cr.moveToFirst();
494             // Latest items should show up first.
495             assertCloudMediaCursor(cr, CLOUD_ID_2, DATE_TAKEN_MS + 1);
496 
497             cr.moveToNext();
498             // If the date taken is the same for 2 or more items, they should be sorted in the order
499             // of their insertion in the database with the latest row inserted first.
500             assertCloudMediaCursor(cr, LOCAL_ID_1, DATE_TAKEN_MS);
501 
502             cr.moveToNext();
503             assertCloudMediaCursor(cr, CLOUD_ID_1, DATE_TAKEN_MS);
504         }
505     }
506 
507     @Test
testRemoveLocal()508     public void testRemoveLocal() throws Exception {
509         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
510 
511         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
512 
513         try (Cursor cr = queryMediaAll(mFacade)) {
514             assertWithMessage(
515                     "Unexpected number of media after addMediaOperation with local media cursor "
516                             + "localCursor.")
517                     .that(cr.getCount()).isEqualTo(1);
518         }
519 
520         assertRemoveMediaOperation(mFacade, LOCAL_PROVIDER, getDeletedMediaCursor(LOCAL_ID), 1);
521 
522         try (Cursor cr = queryMediaAll(mFacade)) {
523             assertWithMessage(
524                     "Unexpected number of media after removeMediaOperation on local provider.")
525                     .that(cr.getCount()).isEqualTo(0);
526         }
527     }
528 
529     @Test
testRemoveLocal_promote()530     public void testRemoveLocal_promote() throws Exception {
531         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
532         Cursor cloudCursor = getCloudMediaCursor(CLOUD_ID, LOCAL_ID, DATE_TAKEN_MS);
533 
534         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
535         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor, 1);
536 
537         try (Cursor cr = queryMediaAll(mFacade)) {
538             assertWithMessage(
539                     "Unexpected number of media after addMediaOperation with one localCursor and "
540                             + "one cloudCursor where "
541                             + "\nlocalCursor has localId = " + LOCAL_ID
542                             + "\ncloudCursor has localId = " + LOCAL_ID + ", cloudId = " + CLOUD_ID)
543                     .that(cr.getCount()).isEqualTo(1);
544             cr.moveToFirst();
545             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS);
546         }
547 
548         assertRemoveMediaOperation(mFacade, LOCAL_PROVIDER, getDeletedMediaCursor(LOCAL_ID), 1);
549 
550         try (Cursor cr = queryMediaAll(mFacade)) {
551             assertWithMessage(
552                     "Unexpected number of media after removeMediaOperation on local provider.")
553                     .that(cr.getCount()).isEqualTo(1);
554             cr.moveToFirst();
555             assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS);
556         }
557     }
558 
559     @Test
testRemoveCloud()560     public void testRemoveCloud() throws Exception {
561         Cursor cloudCursor = getCloudMediaCursor(CLOUD_ID, LOCAL_ID, DATE_TAKEN_MS);
562 
563         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor, 1);
564 
565         try (Cursor cr = queryMediaAll(mFacade)) {
566             assertWithMessage(
567                     "Unexpected number of media after addMediaOperation with cloud media cursor "
568                             + "cloudCursor.")
569                     .that(cr.getCount()).isEqualTo(1);
570         }
571 
572         assertRemoveMediaOperation(mFacade, CLOUD_PROVIDER, getDeletedMediaCursor(CLOUD_ID), 1);
573 
574         try (Cursor cr = queryMediaAll(mFacade)) {
575             assertWithMessage(
576                     "Unexpected number of media after removeMediaOperation on cloud provider.")
577                     .that(cr.getCount()).isEqualTo(0);
578         }
579     }
580 
581     @Test
testRemoveCloud_promote()582     public void testRemoveCloud_promote() throws Exception {
583         Cursor cloudCursor1 = getCloudMediaCursor(CLOUD_ID + "1", LOCAL_ID, DATE_TAKEN_MS + 1);
584         Cursor cloudCursor2 = getCloudMediaCursor(CLOUD_ID + "2", LOCAL_ID, DATE_TAKEN_MS + 2);
585 
586         try (PickerDbFacade.DbWriteOperation operation =
587                      mFacade.beginAddMediaOperation(CLOUD_PROVIDER)) {
588             assertWriteOperation(operation, cloudCursor1, 1);
589             assertWriteOperation(operation, cloudCursor2, 1);
590             operation.setSuccess();
591         }
592 
593         try (Cursor cr = queryMediaAll(mFacade)) {
594             assertWithMessage(
595                     "Unexpected number of media after addMediaOperation with two cloudCursor where "
596                             + "\ncloudCursor1 has localId = " + LOCAL_ID + ", cloudId = " + CLOUD_ID
597                             + "1"
598                             + "\ncloudCursor2 has localId = " + LOCAL_ID + ", cloudId = " + CLOUD_ID
599                             + "2"
600             )
601                     .that(cr.getCount()).isEqualTo(1);
602             cr.moveToFirst();
603             assertCloudMediaCursor(cr, CLOUD_ID + "1", DATE_TAKEN_MS + 1);
604         }
605 
606         assertRemoveMediaOperation(mFacade, CLOUD_PROVIDER,
607                 getDeletedMediaCursor(CLOUD_ID + "1"), 1);
608 
609         try (Cursor cr = queryMediaAll(mFacade)) {
610             assertWithMessage(
611                     "Unexpected number of media after removeMediaOperation on cloud provider.")
612                     .that(cr.getCount()).isEqualTo(1);
613             cr.moveToFirst();
614             assertCloudMediaCursor(cr, CLOUD_ID + "2", DATE_TAKEN_MS + 2);
615         }
616     }
617 
618     @Test
testRemoveHidden()619     public void testRemoveHidden() throws Exception {
620         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
621         Cursor cloudCursor = getCloudMediaCursor(CLOUD_ID, LOCAL_ID, DATE_TAKEN_MS);
622 
623         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor, 1);
624         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
625 
626         try (Cursor cr = queryMediaAll(mFacade)) {
627             assertWithMessage(
628                     "Unexpected number of media after addMediaOperation with one localCursor and "
629                             + "one cloudCursor where "
630                             + "\nlocalCursor has localId = " + LOCAL_ID
631                             + "\ncloudCursor has localId = " + LOCAL_ID + ", cloudId = " + CLOUD_ID)
632                     .that(cr.getCount()).isEqualTo(1);
633             cr.moveToFirst();
634             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS);
635         }
636 
637         assertRemoveMediaOperation(mFacade, CLOUD_PROVIDER, getDeletedMediaCursor(CLOUD_ID), 1);
638 
639         try (Cursor cr = queryMediaAll(mFacade)) {
640             assertWithMessage(
641                     "Unexpected number of media after removeMediaOperation on cloud provider.")
642                     .that(cr.getCount()).isEqualTo(1);
643             cr.moveToFirst();
644             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS);
645         }
646     }
647 
648 
649     @Test
testLocalUpdate()650     public void testLocalUpdate() throws Exception {
651         Cursor localCursor1 = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS + 1);
652         Cursor localCursor2 = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS + 2);
653 
654         try (PickerDbFacade.DbWriteOperation operation =
655                      mFacade.beginAddMediaOperation(LOCAL_PROVIDER)) {
656             assertWriteOperation(operation, localCursor1, 1);
657             assertWriteOperation(operation, localCursor2, 1);
658             operation.setSuccess();
659         }
660 
661         try (Cursor cr = queryMediaAll(mFacade)) {
662             assertWithMessage(
663                     "Unexpected number of media after addMediaOperation with two localCursor where "
664                             + "\nlocalCursor1 has localId = " + LOCAL_ID
665                             + "\nlocalCursor2 has localId = " + LOCAL_ID)
666                     .that(cr.getCount()).isEqualTo(1);
667             cr.moveToFirst();
668             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS + 2);
669         }
670 
671         assertRemoveMediaOperation(mFacade, LOCAL_PROVIDER, getDeletedMediaCursor(LOCAL_ID), 1);
672 
673         try (Cursor cr = queryMediaAll(mFacade)) {
674             assertWithMessage(
675                     "Unexpected number of media after removeMediaOperation on local provider.")
676                     .that(cr.getCount()).isEqualTo(0);
677         }
678     }
679 
680     @Test
testCloudUpdate_withoutLocal()681     public void testCloudUpdate_withoutLocal() throws Exception {
682         Cursor cloudCursor1 = getCloudMediaCursor(CLOUD_ID, LOCAL_ID, DATE_TAKEN_MS + 1);
683         Cursor cloudCursor2 = getCloudMediaCursor(CLOUD_ID, LOCAL_ID, DATE_TAKEN_MS + 2);
684 
685         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor1, 1);
686         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor2, 1);
687 
688         try (Cursor cr = queryMediaAll(mFacade)) {
689             assertWithMessage(
690                     "Unexpected number of media after addMediaOperation with two cloudCursor where "
691                             + "\ncloudCursor1 has localId = " + LOCAL_ID + ", cloudId = " + CLOUD_ID
692                             + "\ncloudCursor2 has localId = " + LOCAL_ID + ", cloudId = " + CLOUD_ID
693             )
694                     .that(cr.getCount()).isEqualTo(1);
695             cr.moveToFirst();
696             assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS + 2);
697         }
698 
699         assertRemoveMediaOperation(mFacade, CLOUD_PROVIDER, getDeletedMediaCursor(CLOUD_ID), 1);
700 
701         try (Cursor cr = queryMediaAll(mFacade)) {
702             assertWithMessage(
703                     "Unexpected number of media after removeMediaOperation on cloud provider.")
704                     .that(cr.getCount()).isEqualTo(0);
705         }
706     }
707 
708     @Test
testCloudUpdate_withLocal()709     public void testCloudUpdate_withLocal() throws Exception {
710         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
711         Cursor cloudCursor1 = getCloudMediaCursor(CLOUD_ID, LOCAL_ID, DATE_TAKEN_MS + 1);
712         Cursor cloudCursor2 = getCloudMediaCursor(CLOUD_ID, LOCAL_ID, DATE_TAKEN_MS + 2);
713 
714         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
715         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor1, 1);
716         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor2, 1);
717 
718         try (Cursor cr = queryMediaAll(mFacade)) {
719             assertWithMessage(
720                     "Unexpected number of media after addMediaOperation with one localCursor and "
721                             + "two cloudCursor, where \nlocalCursor has localId = "
722                             + LOCAL_ID + "\ncloudCursor1 has localId = " + LOCAL_ID + ", cloudId = "
723                             + CLOUD_ID + "\ncloudCursor1 has localId = " + LOCAL_ID + ", cloudId = "
724                             + CLOUD_ID)
725                     .that(cr.getCount()).isEqualTo(1);
726             cr.moveToFirst();
727             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS);
728         }
729 
730         assertRemoveMediaOperation(mFacade, LOCAL_PROVIDER, getDeletedMediaCursor(LOCAL_ID), 1);
731 
732         try (Cursor cr = queryMediaAll(mFacade)) {
733             assertWithMessage(
734                     "Unexpected number of media after removeMediaOperation deleting media with "
735                             + "localId ="
736                             + LOCAL_ID + " from local provider.")
737                     .that(cr.getCount()).isEqualTo(1);
738             cr.moveToFirst();
739             assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS + 2);
740         }
741 
742         assertRemoveMediaOperation(mFacade, CLOUD_PROVIDER, getDeletedMediaCursor(CLOUD_ID), 1);
743 
744         try (Cursor cr = queryMediaAll(mFacade)) {
745             assertWithMessage(
746                     "Unexpected number of media after removeMediaOperation deleting media with "
747                             + "cloudId ="
748                             + CLOUD_ID + " from cloud provider.")
749                     .that(cr.getCount()).isEqualTo(0);
750         }
751     }
752 
753     @Test
testRemoveMedia_withLatestDateTakenMillis()754     public void testRemoveMedia_withLatestDateTakenMillis() {
755         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
756         Cursor cloudCursor1 = getCloudMediaCursor(CLOUD_ID, LOCAL_ID, DATE_TAKEN_MS + 1);
757 
758         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
759         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor1, 1);
760 
761         try (Cursor cr = queryMediaAll(mFacade)) {
762             assertWithMessage(
763                     "Unexpected number of media after addMediaOperation with one localCursor and "
764                             + "one cloudCursor where "
765                             + "\nlocalCursor has localId = " + LOCAL_ID
766                             + "\ncloudCursor1 has localId = " + LOCAL_ID + ", cloudId = "
767                             + CLOUD_ID)
768                     .that(cr.getCount()).isEqualTo(1);
769             cr.moveToFirst();
770             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS);
771         }
772 
773         try (PickerDbFacade.DbWriteOperation operation =
774                      mFacade.beginRemoveMediaOperation(CLOUD_PROVIDER)) {
775             assertWriteOperation(operation, getDeletedMediaCursor(CLOUD_ID), /* writeCount */ 1);
776             assertWithMessage(
777                     "Unexpected value for the firstDateTakenMillis in the columns affected by DB "
778                             + "write operation.")
779                     .that(operation.getFirstDateTakenMillis()).isEqualTo(DATE_TAKEN_MS + 1);
780             operation.setSuccess();
781         }
782 
783         try (PickerDbFacade.DbWriteOperation operation =
784                      mFacade.beginRemoveMediaOperation(LOCAL_PROVIDER)) {
785             assertWriteOperation(operation, getDeletedMediaCursor(LOCAL_ID), /* writeCount */ 1);
786             assertWithMessage(
787                     "Unexpected value for the FirstDateTakenMillis in the columns affected by DB "
788                             + "write operation.")
789                     .that(operation.getFirstDateTakenMillis()).isEqualTo(DATE_TAKEN_MS);
790             operation.setSuccess();
791         }
792 
793         try (Cursor cr = queryMediaAll(mFacade)) {
794             assertWithMessage(
795                     "Unexpected number of media after removeMediaOperation on cloud provider then"
796                             + " on local provider.")
797                     .that(cr.getCount()).isEqualTo(0);
798         }
799     }
800 
801     @Test
testResetLocal()802     public void testResetLocal() throws Exception {
803         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
804         // Add two cloud_ids mapping to the same local_id to verify that
805         // only one gets promoted
806         Cursor cloudCursor1 = getCloudMediaCursor(CLOUD_ID + "1", LOCAL_ID, DATE_TAKEN_MS);
807         Cursor cloudCursor2 = getCloudMediaCursor(CLOUD_ID + "2", LOCAL_ID, DATE_TAKEN_MS);
808 
809         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
810         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor1, 1);
811         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor2, 1);
812 
813         try (Cursor cr = queryMediaAll(mFacade)) {
814             assertWithMessage(
815                     "Unexpected number of media after addMediaOperation with one localCursor and "
816                             + "two cloudCursor, where \nlocalCursor has localId = " + LOCAL_ID
817                             + "\ncloudCursor1 has localId = " + LOCAL_ID + ", cloudId = " + CLOUD_ID
818                             + "1"
819                             + "\ncloudCursor1 has localId = " + LOCAL_ID + ", cloudId = " + CLOUD_ID
820                             + "2")
821                     .that(cr.getCount()).isEqualTo(1);
822             cr.moveToFirst();
823             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS);
824         }
825 
826         assertResetMediaOperation(mFacade, LOCAL_PROVIDER, null, 1);
827 
828         try (Cursor cr = queryMediaAll(mFacade)) {
829             assertWithMessage(
830                     "Unexpected number of media after resetMediaOperation on local provider.")
831                     .that(cr.getCount()).isEqualTo(1);
832             cr.moveToFirst();
833 
834             // Verify that local_id was deleted and either of cloudCursor1 or cloudCursor2
835             // was promoted
836             assertWithMessage("Failed to delete local_Id.")
837                     .that(cr.getString(1)).isNotNull();
838         }
839     }
840 
841     @Test
testResetCloud()842     public void testResetCloud() throws Exception {
843         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
844         Cursor cloudCursor = getCloudMediaCursor(CLOUD_ID, LOCAL_ID, DATE_TAKEN_MS);
845 
846         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
847         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor, 1);
848 
849         try (Cursor cr = queryMediaAll(mFacade)) {
850             assertWithMessage(
851                     "Unexpected number of media after addMediaOperation with one localCursor and "
852                             + "one cloudCursor where "
853                             + "\nlocalCursor has localId = " + LOCAL_ID
854                             + "\ncloudCursor has localId = " + LOCAL_ID + ", cloudId = " + CLOUD_ID)
855                     .that(cr.getCount()).isEqualTo(1);
856             cr.moveToFirst();
857             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS);
858         }
859 
860         assertResetMediaOperation(mFacade, CLOUD_PROVIDER, null, 1);
861 
862         try (Cursor cr = queryMediaAll(mFacade)) {
863             assertWithMessage(
864                     "Unexpected number of media after resetMediaOperation on cloud provider.")
865                     .that(cr.getCount()).isEqualTo(1);
866             cr.moveToFirst();
867             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS);
868         }
869     }
870 
871     @Test
testQueryWithDateTakenFilter()872     public void testQueryWithDateTakenFilter() throws Exception {
873         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
874         Cursor cloudCursor = getCloudMediaCursor(CLOUD_ID, LOCAL_ID, DATE_TAKEN_MS);
875 
876         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
877         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor, 1);
878 
879         try (Cursor cr = queryMediaAll(mFacade)) {
880             assertWithMessage(
881                     "Unexpected number of media after addMediaOperation with one localCursor and "
882                             + "one cloudCursor where "
883                             + "\nlocalCursor has localId = " + LOCAL_ID
884                             + "\ncloudCursor has localId = " + LOCAL_ID + ", cloudId = " + CLOUD_ID)
885                     .that(cr.getCount()).isEqualTo(1);
886         }
887 
888         PickerDbFacade.QueryFilterBuilder qfbBefore = new PickerDbFacade.QueryFilterBuilder(5);
889         qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS - 1);
890         qfbBefore.setId(5);
891         try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) {
892             assertWithMessage(
893                     "Unexpected number of media with dateTakenBeforeMs set to DATE_TAKEN_MS - 1.")
894                     .that(cr.getCount()).isEqualTo(0);
895         }
896 
897         PickerDbFacade.QueryFilterBuilder qfbAfter = new PickerDbFacade.QueryFilterBuilder(5);
898         qfbAfter.setDateTakenAfterMs(DATE_TAKEN_MS + 1);
899         qfbAfter.setId(5);
900         try (Cursor cr = mFacade.queryMediaForUi(qfbAfter.build())) {
901             assertWithMessage(
902                     "Unexpected number of media with dateTakenAfterMs set to DATE_TAKEN_MS + 1.")
903                     .that(cr.getCount()).isEqualTo(0);
904         }
905     }
906 
907     @Test
testQueryWithIdFilter()908     public void testQueryWithIdFilter() throws Exception {
909         Cursor cursor1 = getLocalMediaCursor(LOCAL_ID + "1", DATE_TAKEN_MS);
910         Cursor cursor2 = getLocalMediaCursor(LOCAL_ID + "2", DATE_TAKEN_MS);
911 
912         try (PickerDbFacade.DbWriteOperation operation =
913                      mFacade.beginAddMediaOperation(LOCAL_PROVIDER)) {
914             assertWriteOperation(operation, cursor1, 1);
915             assertWriteOperation(operation, cursor2, 1);
916             operation.setSuccess();
917         }
918 
919         PickerDbFacade.QueryFilterBuilder qfbBefore = new PickerDbFacade.QueryFilterBuilder(5);
920         qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS);
921         qfbBefore.setId(2);
922         try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) {
923             assertWithMessage("Unexpected number of media with Id set to 2.")
924                     .that(cr.getCount()).isEqualTo(1);
925 
926             cr.moveToFirst();
927             assertCloudMediaCursor(cr, LOCAL_ID + "1", DATE_TAKEN_MS);
928         }
929 
930         PickerDbFacade.QueryFilterBuilder qfbAfter = new PickerDbFacade.QueryFilterBuilder(5);
931         qfbAfter.setDateTakenAfterMs(DATE_TAKEN_MS);
932         qfbAfter.setId(1);
933         try (Cursor cr = mFacade.queryMediaForUi(qfbAfter.build())) {
934             assertWithMessage("Unexpected number of media with Id set to 1.")
935                     .that(cr.getCount()).isEqualTo(1);
936 
937             cr.moveToFirst();
938             assertCloudMediaCursor(cr, LOCAL_ID + "2", DATE_TAKEN_MS);
939         }
940     }
941 
942     @Test
testQueryWithLimit()943     public void testQueryWithLimit() throws Exception {
944         Cursor cursor1 = getLocalMediaCursor(LOCAL_ID + "1", DATE_TAKEN_MS);
945         Cursor cursor2 = getCloudMediaCursor(CLOUD_ID + "2", null, DATE_TAKEN_MS);
946         Cursor cursor3 = getLocalMediaCursor(LOCAL_ID + "3", DATE_TAKEN_MS);
947 
948         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor1, 1);
949         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor2, 1);
950         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor3, 1);
951 
952         PickerDbFacade.QueryFilterBuilder qfbBefore = new PickerDbFacade.QueryFilterBuilder(1);
953         qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS + 1);
954         qfbBefore.setId(0);
955         try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) {
956             assertWithMessage(
957                     "Unexpected number of media with limit set to 1 and dateTakenBeforeMs set to "
958                             + "DATE_TAKEN_MS + 1.")
959                     .that(cr.getCount()).isEqualTo(1);
960 
961             cr.moveToFirst();
962             assertCloudMediaCursor(cr, LOCAL_ID + "3", DATE_TAKEN_MS);
963         }
964 
965         PickerDbFacade.QueryFilterBuilder qfbAfter = new PickerDbFacade.QueryFilterBuilder(1);
966         qfbAfter.setDateTakenAfterMs(DATE_TAKEN_MS - 1);
967         qfbAfter.setId(0);
968         try (Cursor cr = mFacade.queryMediaForUi(qfbAfter.build())) {
969             assertWithMessage(
970                     "Unexpected number of media with limit set to 1 and dateTakenAfterMs set to "
971                             + "DATE_TAKEN_MS - 1.")
972                     .that(cr.getCount()).isEqualTo(1);
973 
974             cr.moveToFirst();
975             assertCloudMediaCursor(cr, LOCAL_ID + "3", DATE_TAKEN_MS);
976         }
977 
978         try (Cursor cr = mFacade.queryMediaForUi(
979                 new PickerDbFacade.QueryFilterBuilder(1).build())) {
980             assertWithMessage("Unexpected number of media with limit set to 1.")
981                     .that(cr.getCount()).isEqualTo(1);
982 
983             cr.moveToFirst();
984             assertCloudMediaCursor(cr, LOCAL_ID + "3", DATE_TAKEN_MS);
985         }
986     }
987 
988     @Test
testQueryWithSizeFilter()989     public void testQueryWithSizeFilter() throws Exception {
990         Cursor cursor1 = getMediaCursor(LOCAL_ID, DATE_TAKEN_MS, GENERATION_MODIFIED,
991                 /* mediaStoreUri */ null, /* sizeBytes */ 1, MP4_VIDEO_MIME_TYPE,
992                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
993         Cursor cursor2 = getMediaCursor(CLOUD_ID, DATE_TAKEN_MS, GENERATION_MODIFIED,
994                 /* mediaStoreUri */ null, /* sizeBytes */ 2, MP4_VIDEO_MIME_TYPE,
995                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
996 
997         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor1, 1);
998         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor2, 1);
999 
1000         // Verify all
1001         PickerDbFacade.QueryFilterBuilder qfbAll = new PickerDbFacade.QueryFilterBuilder(1000);
1002         qfbAll.setSizeBytes(10);
1003         try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) {
1004             assertWithMessage("Unexpected number of media with sizeBytes set to 10.")
1005                     .that(cr.getCount()).isEqualTo(2);
1006         }
1007 
1008         qfbAll.setSizeBytes(1);
1009         try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) {
1010             assertWithMessage("Unexpected number of media with sizeBytes set to 1.")
1011                     .that(cr.getCount()).isEqualTo(1);
1012 
1013             cr.moveToFirst();
1014             assertCloudMediaCursor(cr, LOCAL_ID, MP4_VIDEO_MIME_TYPE);
1015         }
1016 
1017         // Verify after
1018         PickerDbFacade.QueryFilterBuilder qfbAfter = new PickerDbFacade.QueryFilterBuilder(1000);
1019         qfbAfter.setDateTakenAfterMs(DATE_TAKEN_MS - 1);
1020         qfbAfter.setId(0);
1021         qfbAfter.setSizeBytes(10);
1022         try (Cursor cr = mFacade.queryMediaForUi(qfbAfter.build())) {
1023             assertWithMessage(
1024                     "Unexpected number of media with sizeBytes set to 10 and dateTakenAfterMs set"
1025                             + " to DATE_TAKEN_MS - 1.")
1026                     .that(cr.getCount()).isEqualTo(2);
1027         }
1028 
1029         qfbAfter.setDateTakenAfterMs(DATE_TAKEN_MS - 1);
1030         qfbAfter.setId(0);
1031         qfbAfter.setSizeBytes(1);
1032         try (Cursor cr = mFacade.queryMediaForUi(qfbAfter.build())) {
1033             assertWithMessage(
1034                     "Unexpected number of media with sizeBytes set to 1 and dateTakenAfterMs set "
1035                             + "to DATE_TAKEN_MS - 1.")
1036                     .that(cr.getCount()).isEqualTo(1);
1037 
1038             cr.moveToFirst();
1039             assertCloudMediaCursor(cr, LOCAL_ID, MP4_VIDEO_MIME_TYPE);
1040         }
1041 
1042         // Verify before
1043         PickerDbFacade.QueryFilterBuilder qfbBefore = new PickerDbFacade.QueryFilterBuilder(1000);
1044         qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS + 1);
1045         qfbBefore.setId(0);
1046         qfbBefore.setSizeBytes(10);
1047         try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) {
1048             assertWithMessage(
1049                     "Unexpected number of media with sizeBytes set to 10 and dateTakenBeforeMs "
1050                             + "set to DATE_TAKEN_MS + 1.")
1051                     .that(cr.getCount()).isEqualTo(2);
1052         }
1053 
1054         qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS + 1);
1055         qfbBefore.setId(0);
1056         qfbBefore.setSizeBytes(1);
1057         try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) {
1058             assertWithMessage(
1059                     "Unexpected number of media with sizeBytes set to 1 and dateTakenBeforeMs set"
1060                             + " to DATE_TAKEN_MS + 1.")
1061                     .that(cr.getCount()).isEqualTo(1);
1062 
1063             cr.moveToFirst();
1064             assertCloudMediaCursor(cr, LOCAL_ID, MP4_VIDEO_MIME_TYPE);
1065         }
1066     }
1067 
1068     @Test
testQueryWithMimeTypesFilter()1069     public void testQueryWithMimeTypesFilter() throws Exception {
1070         Cursor cursor1 = getMediaCursor(LOCAL_ID_1, DATE_TAKEN_MS, GENERATION_MODIFIED,
1071                 /* mediaStoreUri */ null, SIZE_BYTES, WEBM_VIDEO_MIME_TYPE,
1072                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1073         Cursor cursor2 = getMediaCursor(LOCAL_ID_2, DATE_TAKEN_MS, GENERATION_MODIFIED,
1074                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
1075                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1076         Cursor cursor3 = getMediaCursor(CLOUD_ID_1, DATE_TAKEN_MS, GENERATION_MODIFIED,
1077                 /* mediaStoreUri */ null, SIZE_BYTES, PNG_IMAGE_MIME_TYPE,
1078                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1079         Cursor cursor4 = getMediaCursor(CLOUD_ID_2, DATE_TAKEN_MS, GENERATION_MODIFIED,
1080                 /* mediaStoreUri */ null, SIZE_BYTES, GIF_IMAGE_MIME_TYPE,
1081                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1082         Cursor cursor5 = getMediaCursor(CLOUD_ID_3, DATE_TAKEN_MS - 1, GENERATION_MODIFIED,
1083                 /* mediaStoreUri */ null, SIZE_BYTES, PNG_IMAGE_MIME_TYPE,
1084                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1085         Cursor cursor6 = getMediaCursor(LOCAL_ID_3, DATE_TAKEN_MS + 1, GENERATION_MODIFIED,
1086                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1087                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1088 
1089         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor1, 1);
1090         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor2, 1);
1091         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor3, 1);
1092         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor4, 1);
1093         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor5, 1);
1094         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor6, 1);
1095 
1096         // Verify all
1097         PickerDbFacade.QueryFilterBuilder qfbAll = new PickerDbFacade.QueryFilterBuilder(1000);
1098         qfbAll.setMimeTypes(new String[]{"*/*"});
1099         try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) {
1100             assertWithMessage(
1101                     "Unexpected number of rows with mime_type filter set to {\"*/*\"}")
1102                     .that(cr.getCount()).isEqualTo(6);
1103         }
1104 
1105         qfbAll.setMimeTypes(new String[]{"image/*"});
1106         try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) {
1107             assertWithMessage(
1108                     "Unexpected number of rows with mime_type filter set to {\"image/*\"}")
1109                     .that(cr.getCount()).isEqualTo(4);
1110 
1111             assertAllMediaCursor(cr,
1112                     new String[]{CLOUD_ID_2, CLOUD_ID_1, LOCAL_ID_2, CLOUD_ID_3},
1113                     new long[]{DATE_TAKEN_MS, DATE_TAKEN_MS, DATE_TAKEN_MS, DATE_TAKEN_MS - 1},
1114                     new String[]{GIF_IMAGE_MIME_TYPE, PNG_IMAGE_MIME_TYPE,
1115                             JPEG_IMAGE_MIME_TYPE, PNG_IMAGE_MIME_TYPE});
1116         }
1117 
1118         qfbAll.setMimeTypes(new String[]{"video/*"});
1119         try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) {
1120             assertWithMessage(
1121                     "Unexpected number of rows with mime_type filter set to {\"video/*\"}")
1122                     .that(cr.getCount()).isEqualTo(2);
1123 
1124             assertAllMediaCursor(cr,
1125                     new String[]{LOCAL_ID_3, LOCAL_ID_1},
1126                     new long[]{DATE_TAKEN_MS + 1, DATE_TAKEN_MS},
1127                     new String[]{MP4_VIDEO_MIME_TYPE, WEBM_VIDEO_MIME_TYPE});
1128         }
1129 
1130         // Verify after
1131         PickerDbFacade.QueryFilterBuilder qfbAfter = new PickerDbFacade.QueryFilterBuilder(1000);
1132         qfbAfter.setDateTakenAfterMs(DATE_TAKEN_MS);
1133         qfbAfter.setId(0);
1134         qfbAfter.setMimeTypes(new String[]{"image/*"});
1135         try (Cursor cr = mFacade.queryMediaForUi(qfbAfter.build())) {
1136             assertWithMessage(
1137                     "Unexpected number of rows with mime_type filter set to {\"image/*\"} "
1138                             + "and date taken after set to DATE_TAKEN_MS")
1139                     .that(cr.getCount()).isEqualTo(3);
1140         }
1141 
1142         qfbAfter.setDateTakenAfterMs(DATE_TAKEN_MS - 1);
1143         qfbAfter.setId(0);
1144         qfbAfter.setMimeTypes(new String[]{PNG_IMAGE_MIME_TYPE});
1145         try (Cursor cr = mFacade.queryMediaForUi(qfbAfter.build())) {
1146             assertWithMessage(
1147                     "Unexpected number of rows with mime_type filter set to "
1148                             + "{PNG_IMAGE_MIME_TYPE} and date taken after set to DATE_TAKEN_MS - 1")
1149                     .that(cr.getCount()).isEqualTo(2);
1150 
1151             assertAllMediaCursor(cr,
1152                     new String[]{CLOUD_ID_1, CLOUD_ID_3},
1153                     new long[]{DATE_TAKEN_MS, DATE_TAKEN_MS - 1},
1154                     new String[]{PNG_IMAGE_MIME_TYPE, PNG_IMAGE_MIME_TYPE});
1155         }
1156 
1157         qfbAfter.setDateTakenAfterMs(DATE_TAKEN_MS - 1);
1158         qfbAfter.setId(0);
1159         qfbAfter.setMimeTypes(new String[]{"video/*"});
1160         try (Cursor cr = mFacade.queryMediaForUi(qfbAfter.build())) {
1161             assertWithMessage(
1162                     "Unexpected number of rows with mime_type filter set to {\"video/*\"} "
1163                             + "and date taken after set to DATE_TAKEN_MS - 1")
1164                     .that(cr.getCount()).isEqualTo(2);
1165 
1166             assertAllMediaCursor(cr,
1167                     new String[]{LOCAL_ID_3, LOCAL_ID_1},
1168                     new long[]{DATE_TAKEN_MS + 1, DATE_TAKEN_MS},
1169                     new String[]{MP4_VIDEO_MIME_TYPE, WEBM_VIDEO_MIME_TYPE});
1170         }
1171 
1172         // Verify before
1173         PickerDbFacade.QueryFilterBuilder qfbBefore = new PickerDbFacade.QueryFilterBuilder(1000);
1174         qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS + 1);
1175         qfbBefore.setId(0);
1176         qfbBefore.setMimeTypes(new String[]{"*/*"});
1177         try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) {
1178             assertWithMessage(
1179                     "Unexpected number of rows with mime_type filter set to {\"*/*\"} and "
1180                             + "date taken before set to DATE_TAKEN_MS + 1")
1181                     .that(cr.getCount()).isEqualTo(5);
1182         }
1183 
1184         qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS + 1);
1185         qfbBefore.setId(0);
1186         qfbBefore.setMimeTypes(new String[]{"video/*"});
1187         try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) {
1188             assertWithMessage(
1189                     "Unexpected number of rows with mime_type filter set to {\"video/*\"} "
1190                             + "and date taken before set to DATE_TAKEN_MS + 1")
1191                     .that(cr.getCount()).isEqualTo(1);
1192 
1193             cr.moveToFirst();
1194             assertCloudMediaCursor(cr, LOCAL_ID_1, DATE_TAKEN_MS, WEBM_VIDEO_MIME_TYPE);
1195         }
1196 
1197         qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS + 2);
1198         qfbBefore.setId(0);
1199         qfbBefore.setMimeTypes(new String[]{"video/*"});
1200         try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) {
1201             assertWithMessage(
1202                     "Unexpected number of rows with mime_type filter set to {\"video/*\"} "
1203                             + "and date taken before set to DATE_TAKEN_MS + 2")
1204                     .that(cr.getCount()).isEqualTo(2);
1205 
1206             assertAllMediaCursor(cr,
1207                     new String[]{LOCAL_ID_3, LOCAL_ID_1},
1208                     new long[]{DATE_TAKEN_MS + 1, DATE_TAKEN_MS},
1209                     new String[]{MP4_VIDEO_MIME_TYPE, WEBM_VIDEO_MIME_TYPE});
1210         }
1211 
1212         qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS + 1);
1213         qfbBefore.setId(0);
1214         qfbBefore.setMimeTypes(new String[]{PNG_IMAGE_MIME_TYPE});
1215         try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) {
1216             assertWithMessage(
1217                     "Unexpected number of rows with mime_type filter set to "
1218                             + "{PNG_IMAGE_MIME_TYPE} and date taken before set to DATE_TAKEN_MS +"
1219                             + " 1")
1220                     .that(cr.getCount()).isEqualTo(2);
1221 
1222             assertAllMediaCursor(cr,
1223                     new String[]{CLOUD_ID_1, CLOUD_ID_3},
1224                     new long[]{DATE_TAKEN_MS, DATE_TAKEN_MS - 1},
1225                     new String[]{PNG_IMAGE_MIME_TYPE, PNG_IMAGE_MIME_TYPE});
1226         }
1227     }
1228 
1229     @Test
testQueryWithMultipleMimeTypesFilter()1230     public void testQueryWithMultipleMimeTypesFilter() throws Exception {
1231         Cursor cursor1 = getMediaCursor(LOCAL_ID_1, DATE_TAKEN_MS, GENERATION_MODIFIED,
1232                 /* mediaStoreUri */ null, SIZE_BYTES, WEBM_VIDEO_MIME_TYPE,
1233                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1234         Cursor cursor2 = getMediaCursor(LOCAL_ID_2, DATE_TAKEN_MS, GENERATION_MODIFIED,
1235                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
1236                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1237         Cursor cursor3 = getMediaCursor(LOCAL_ID_3, DATE_TAKEN_MS, GENERATION_MODIFIED,
1238                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1239                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1240         Cursor cursor4 = getMediaCursor(CLOUD_ID_1, DATE_TAKEN_MS, GENERATION_MODIFIED,
1241                 /* mediaStoreUri */ null, SIZE_BYTES, PNG_IMAGE_MIME_TYPE,
1242                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1243         Cursor cursor5 = getMediaCursor(CLOUD_ID_2, DATE_TAKEN_MS, GENERATION_MODIFIED,
1244                 /* mediaStoreUri */ null, SIZE_BYTES, GIF_IMAGE_MIME_TYPE,
1245                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1246         Cursor cursor6 = getMediaCursor(CLOUD_ID_3, DATE_TAKEN_MS, GENERATION_MODIFIED,
1247                 /* mediaStoreUri */ null, SIZE_BYTES, MPEG_VIDEO_MIME_TYPE,
1248                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1249         Cursor cursor7 = getMediaCursor(CLOUD_ID_4, DATE_TAKEN_MS - 1, GENERATION_MODIFIED,
1250                 /* mediaStoreUri */ null, SIZE_BYTES, PNG_IMAGE_MIME_TYPE,
1251                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1252         Cursor cursor8 = getMediaCursor(LOCAL_ID_4, DATE_TAKEN_MS + 1, GENERATION_MODIFIED,
1253                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1254                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1255 
1256         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor1, 1);
1257         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor2, 1);
1258         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor3, 1);
1259         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor4, 1);
1260         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor5, 1);
1261         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor6, 1);
1262         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor7, 1);
1263         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor8, 1);
1264 
1265         // Verify all
1266         PickerDbFacade.QueryFilterBuilder qfbAll = new PickerDbFacade.QueryFilterBuilder(1000);
1267         qfbAll.setMimeTypes(new String[]{"*/*"});
1268         try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) {
1269             assertWithMessage(
1270                     "Unexpected number of rows with mime_type filter set to {\"*/*\"}")
1271                     .that(cr.getCount()).isEqualTo(8);
1272         }
1273 
1274         qfbAll.setMimeTypes(new String[]{"image/*", PNG_IMAGE_MIME_TYPE, MP4_VIDEO_MIME_TYPE});
1275         try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) {
1276             assertWithMessage(
1277                     "Unexpected number of rows with mime_type filter set to {\"image/*\","
1278                             + "PNG_IMAGE_MIME_TYPE ,PNG_IMAGE_MIME_TYPE}")
1279                     .that(cr.getCount()).isEqualTo(6);
1280         }
1281 
1282         qfbAll.setMimeTypes(new String[]{GIF_IMAGE_MIME_TYPE, MPEG_VIDEO_MIME_TYPE,
1283                 WEBM_VIDEO_MIME_TYPE});
1284         try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) {
1285             assertWithMessage(
1286                     "Unexpected number of rows with mime_type filter set to "
1287                             + "{GIF_IMAGE_MIME_TYPE, MPEG_VIDEO_MIME_TYPE, WEBM_VIDEO_MIME_TYPE}")
1288                     .that(cr.getCount()).isEqualTo(3);
1289 
1290             assertAllMediaCursor(cr, new String[]{CLOUD_ID_3, CLOUD_ID_2, LOCAL_ID_1},
1291                     new long[]{DATE_TAKEN_MS, DATE_TAKEN_MS, DATE_TAKEN_MS}, new String[]{
1292                             MPEG_VIDEO_MIME_TYPE, GIF_IMAGE_MIME_TYPE, WEBM_VIDEO_MIME_TYPE});
1293         }
1294 
1295         // Verify after
1296         PickerDbFacade.QueryFilterBuilder qfbAfter = new PickerDbFacade.QueryFilterBuilder(1000);
1297 
1298         qfbAfter.setDateTakenAfterMs(DATE_TAKEN_MS - 1);
1299         qfbAfter.setId(0);
1300         qfbAfter.setMimeTypes(new String[]{"video/*"});
1301         try (Cursor cr = mFacade.queryMediaForUi(qfbAfter.build())) {
1302             assertWithMessage(
1303                     "Unexpected number of rows with mime_type filter set to {\"video/*\"} "
1304                             + "and date taken after set to DATE_TAKEN_MS - 1")
1305                     .that(cr.getCount()).isEqualTo(4);
1306         }
1307 
1308         qfbAfter.setDateTakenAfterMs(DATE_TAKEN_MS - 1);
1309         qfbAfter.setId(0);
1310         qfbAfter.setMimeTypes(new String[]{GIF_IMAGE_MIME_TYPE,
1311                 MPEG_VIDEO_MIME_TYPE, WEBM_VIDEO_MIME_TYPE, M4V_VIDEO_MIME_TYPE});
1312         try (Cursor cr = mFacade.queryMediaForUi(qfbAfter.build())) {
1313             assertWithMessage(
1314                     "Unexpected number of rows with mime_type filter set to "
1315                             + "{GIF_IMAGE_MIME_TYPE, MPEG_VIDEO_MIME_TYPE, WEBM_VIDEO_MIME_TYPE, "
1316                             + "M4V_VIDEO_MIME_TYPE} and date taken after set to DATE_TAKEN_MS - 1")
1317                     .that(cr.getCount()).isEqualTo(3);
1318 
1319             assertAllMediaCursor(cr, new String[]{CLOUD_ID_3, CLOUD_ID_2, LOCAL_ID_1},
1320                     new long[]{DATE_TAKEN_MS, DATE_TAKEN_MS, DATE_TAKEN_MS}, new String[]{
1321                             MPEG_VIDEO_MIME_TYPE, GIF_IMAGE_MIME_TYPE, WEBM_VIDEO_MIME_TYPE});
1322         }
1323 
1324         qfbAfter.setDateTakenAfterMs(DATE_TAKEN_MS - 1);
1325         qfbAfter.setId(0);
1326         qfbAfter.setMimeTypes(new String[]{GIF_IMAGE_MIME_TYPE, MP4_VIDEO_MIME_TYPE});
1327         try (Cursor cr = mFacade.queryMediaForUi(qfbAfter.build())) {
1328             assertWithMessage(
1329                     "Unexpected number of rows with mime_type filter set to "
1330                             + "{GIF_IMAGE_MIME_TYPE, MP4_VIDEO_MIME_TYPE} and date taken after "
1331                             + "set to DATE_TAKEN_MS - 1")
1332                     .that(cr.getCount()).isEqualTo(3);
1333         }
1334 
1335         // Verify before
1336         PickerDbFacade.QueryFilterBuilder qfbBefore = new PickerDbFacade.QueryFilterBuilder(1000);
1337         qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS + 1);
1338         qfbBefore.setId(0);
1339         qfbBefore.setMimeTypes(new String[]{"*/*"});
1340         try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) {
1341             assertWithMessage(
1342                     "Unexpected number of rows with mime_type filter set to {\"*/*\"} and "
1343                             + "date taken before set to DATE_TAKEN_MS + 1")
1344                     .that(cr.getCount()).isEqualTo(7);
1345         }
1346 
1347         qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS);
1348         qfbBefore.setId(0);
1349         qfbBefore.setMimeTypes(new String[]{"image/*"});
1350         try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) {
1351             assertWithMessage(
1352                     "Unexpected number of rows with mime_type filter set to {\"image/*\"} "
1353                             + "and date taken before set to DATE_TAKEN_MS")
1354                     .that(cr.getCount()).isEqualTo(1);
1355 
1356             cr.moveToFirst();
1357             assertCloudMediaCursor(cr, CLOUD_ID_4, DATE_TAKEN_MS - 1, PNG_IMAGE_MIME_TYPE);
1358         }
1359 
1360         qfbBefore.setDateTakenBeforeMs(DATE_TAKEN_MS + 2);
1361         qfbBefore.setId(0);
1362         qfbBefore.setMimeTypes(new String[]{MP4_VIDEO_MIME_TYPE, GIF_IMAGE_MIME_TYPE});
1363         try (Cursor cr = mFacade.queryMediaForUi(qfbBefore.build())) {
1364             assertWithMessage(
1365                     "Unexpected number of rows with mime_type filter set to "
1366                             + "{MP4_VIDEO_MIME_TYPE, GIF_IMAGE_MIME_TYPE} and date taken before "
1367                             + "set to DATE_TAKEN_MS + 2")
1368                     .that(cr.getCount()).isEqualTo(3);
1369 
1370             assertAllMediaCursor(cr, new String[]{LOCAL_ID_4, CLOUD_ID_2, LOCAL_ID_3},
1371                     new long[]{DATE_TAKEN_MS + 1, DATE_TAKEN_MS, DATE_TAKEN_MS}, new String[]{
1372                             MP4_VIDEO_MIME_TYPE, GIF_IMAGE_MIME_TYPE, MP4_VIDEO_MIME_TYPE});
1373         }
1374     }
1375 
1376     @Test
testQueryWithSizeAndMimeTypesFilter()1377     public void testQueryWithSizeAndMimeTypesFilter() throws Exception {
1378         Cursor cursor1 = getMediaCursor(LOCAL_ID, DATE_TAKEN_MS, GENERATION_MODIFIED,
1379                 /* mediaStoreUri */ null, /* sizeBytes */ 2, WEBM_VIDEO_MIME_TYPE,
1380                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1381         Cursor cursor2 = getMediaCursor(CLOUD_ID, DATE_TAKEN_MS, GENERATION_MODIFIED,
1382                 /* mediaStoreUri */ null, /* sizeBytes */ 1, MP4_VIDEO_MIME_TYPE,
1383                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1384 
1385         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, cursor1, 1);
1386         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cursor2, 1);
1387 
1388         // mime_type and size filter matches all
1389         PickerDbFacade.QueryFilterBuilder qfbAll = new PickerDbFacade.QueryFilterBuilder(1000);
1390         qfbAll.setMimeTypes(new String[]{"*/*"});
1391         qfbAll.setSizeBytes(10);
1392         try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) {
1393             assertWithMessage(
1394                     "Unexpected number of rows with mime_type filter set to {\"*/*\"} and size "
1395                             + "filter set to 10 bytes")
1396                     .that(cr.getCount()).isEqualTo(2);
1397         }
1398 
1399         // mime_type and size filter matches none
1400         qfbAll.setMimeTypes(new String[]{WEBM_VIDEO_MIME_TYPE});
1401         qfbAll.setSizeBytes(1);
1402         try (Cursor cr = mFacade.queryMediaForUi(qfbAll.build())) {
1403             assertWithMessage(
1404                     "Unexpected number of rows with mime_type filter set to "
1405                             + "{WEBM_VIDEO_MIME_TYPE} and size filter set to 1 byte")
1406                     .that(cr.getCount()).isEqualTo(0);
1407         }
1408     }
1409 
1410     @Test
testQueryMediaId()1411     public void testQueryMediaId() throws Exception {
1412         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
1413         Cursor cloudCursor = getCloudMediaCursor(CLOUD_ID, /* localId */ null, DATE_TAKEN_MS);
1414 
1415         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
1416         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor, 1);
1417 
1418         // Assert all projection columns
1419         final String[] allProjection = mProjectionHelper.getProjectionMap(
1420                 PickerMediaColumns.class).keySet().toArray(new String[0]);
1421         try (Cursor cr = mFacade.queryMediaIdForApps(PickerUriResolver.PICKER_SEGMENT,
1422                 LOCAL_PROVIDER, LOCAL_ID, allProjection)) {
1423             assertWithMessage(
1424                     "Unexpected number of rows when asserting all projection columns with "
1425                             + "PickerUriResolver as PICKER_SEGMENT on local provider.")
1426                     .that(cr.getCount()).isEqualTo(1);
1427 
1428             cr.moveToFirst();
1429             assertMediaStoreCursor(cr, LOCAL_ID, DATE_TAKEN_MS, PickerUriResolver.PICKER_SEGMENT);
1430         }
1431 
1432         try (Cursor cr = mFacade.queryMediaIdForApps(PickerUriResolver.PICKER_GET_CONTENT_SEGMENT,
1433                 LOCAL_PROVIDER, LOCAL_ID, allProjection)) {
1434             assertWithMessage(
1435                     "Unexpected number of rows when asserting all projection columns with "
1436                             + "PickerUriResolver as PICKER_GET_CONTENT_SEGMENT on local provider.")
1437                     .that(cr.getCount()).isEqualTo(1);
1438 
1439             cr.moveToFirst();
1440             assertMediaStoreCursor(cr, LOCAL_ID, DATE_TAKEN_MS,
1441                     PickerUriResolver.PICKER_GET_CONTENT_SEGMENT);
1442         }
1443 
1444         // Assert one projection column
1445         final String[] oneProjection = new String[]{PickerMediaColumns.DATE_TAKEN};
1446 
1447         try (Cursor cr = mFacade.queryMediaIdForApps(PickerUriResolver.PICKER_SEGMENT,
1448                 CLOUD_PROVIDER, CLOUD_ID, oneProjection)) {
1449             assertWithMessage(
1450                     "Unexpected number of rows when asserting one projection column with cloud "
1451                             + "provider.")
1452                     .that(cr.getCount()).isEqualTo(1);
1453 
1454             cr.moveToFirst();
1455             assertWithMessage(
1456                     "Unexpected value of PickerMediaColumns.DATE_TAKEN with cloud provider.")
1457                     .that(cr.getLong(cr.getColumnIndexOrThrow(PickerMediaColumns.DATE_TAKEN)))
1458                     .isEqualTo(DATE_TAKEN_MS);
1459         }
1460 
1461         // Assert invalid projection column
1462         final String invalidColumn = "test invalid column";
1463         final String[] invalidProjection = new String[]{
1464                 PickerMediaColumns.DATE_TAKEN,
1465                 invalidColumn
1466         };
1467 
1468         try (Cursor cr = mFacade.queryMediaIdForApps(PickerUriResolver.PICKER_SEGMENT,
1469                 CLOUD_PROVIDER, CLOUD_ID, invalidProjection)) {
1470             assertWithMessage(
1471                     "Unexpected number of rows when asserting invalid projection column with "
1472                             + "cloud provider.")
1473                     .that(cr.getCount()).isEqualTo(1);
1474             assertWithMessage("Unexpected number of columns in cursor")
1475                     .that(cr.getColumnCount())
1476                     .isEqualTo(2);
1477 
1478             cr.moveToFirst();
1479             assertWithMessage("Unexpected value of the invalidColumn with cloud provider.")
1480                     .that(cr.getLong(cr.getColumnIndexOrThrow(invalidColumn)))
1481                     .isEqualTo(0);
1482             assertWithMessage("Unexpected value of the invalidColumn with cloud provider.")
1483                     .that(cr.getString(cr.getColumnIndexOrThrow(invalidColumn)))
1484                     .isEqualTo(null);
1485             assertWithMessage(
1486                     "Unexpected value of PickerMediaColumns.DATE_TAKEN with cloud provider.")
1487                     .that(cr.getLong(cr.getColumnIndexOrThrow(PickerMediaColumns.DATE_TAKEN)))
1488                     .isEqualTo(DATE_TAKEN_MS);
1489         }
1490     }
1491 
1492     /**
1493      * Tests {@link PickerDbFacade#queryMediaForUi(PickerDbFacade.QueryFilter)}
1494      * to ensure columns not required for the UI are not present.
1495      */
1496     @Test
testQueryMediaForUi()1497     public void testQueryMediaForUi() throws Exception {
1498 
1499         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
1500         Cursor cloudCursor = getCloudMediaCursor(CLOUD_ID, /* localId */ null, DATE_TAKEN_MS);
1501 
1502         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
1503         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor, 1);
1504 
1505         PickerDbFacade.QueryFilterBuilder qfb =
1506                 new PickerDbFacade.QueryFilterBuilder(/* limit */ 1000);
1507         try (Cursor cr = mFacade.queryMediaForUi(qfb.build())) {
1508 
1509             assertWithMessage(
1510                     "Unexpected number of rows on queryMediaForUi.")
1511                     .that(cr.getCount()).isEqualTo(2);
1512             cr.moveToFirst();
1513             assertThrows(
1514                     IllegalArgumentException.class,
1515                     () -> cr.getColumnIndexOrThrow(MediaColumns.WIDTH));
1516             assertThrows(
1517                     IllegalArgumentException.class,
1518                     () -> cr.getColumnIndexOrThrow(MediaColumns.HEIGHT));
1519             assertThrows(
1520                     IllegalArgumentException.class,
1521                     () -> cr.getColumnIndexOrThrow(MediaColumns.ORIENTATION));
1522 
1523             cr.moveToNext();
1524             assertThrows(
1525                     IllegalArgumentException.class,
1526                     () -> cr.getColumnIndexOrThrow(MediaColumns.WIDTH));
1527             assertThrows(
1528                     IllegalArgumentException.class,
1529                     () -> cr.getColumnIndexOrThrow(MediaColumns.HEIGHT));
1530             assertThrows(
1531                     IllegalArgumentException.class,
1532                     () -> cr.getColumnIndexOrThrow(MediaColumns.ORIENTATION));
1533         }
1534     }
1535 
1536     /**
1537      * Tests {@link PickerDbFacade#queryAlbumMediaForUi(PickerDbFacade.QueryFilter, String)} to
1538      * ensure columns not required for the UI are not present.
1539      */
1540     @Test
testQueryAlbumMediaForUi()1541     public void testQueryAlbumMediaForUi() throws Exception {
1542 
1543         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
1544         Cursor cloudCursor = getCloudMediaCursor(CLOUD_ID, /* localId */ null, DATE_TAKEN_MS);
1545 
1546         assertAddAlbumMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1, ALBUM_ID);
1547         assertAddAlbumMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor, 1, ALBUM_ID);
1548 
1549         PickerDbFacade.QueryFilterBuilder localQfb =
1550                 new PickerDbFacade.QueryFilterBuilder(/* limit */ 1000);
1551         try (Cursor cr =
1552                      mFacade.queryAlbumMediaForUi(
1553                              localQfb.setAlbumId(ALBUM_ID).build(), LOCAL_PROVIDER)) {
1554             assertWithMessage(
1555                     "Unexpected number of rows on queryAlbumMediaForUi with local provider.")
1556                     .that(cr.getCount()).isEqualTo(1);
1557             cr.moveToFirst();
1558             assertThrows(
1559                     IllegalArgumentException.class,
1560                     () -> cr.getColumnIndexOrThrow(MediaColumns.WIDTH));
1561             assertThrows(
1562                     IllegalArgumentException.class,
1563                     () -> cr.getColumnIndexOrThrow(MediaColumns.HEIGHT));
1564             assertThrows(
1565                     IllegalArgumentException.class,
1566                     () -> cr.getColumnIndexOrThrow(MediaColumns.ORIENTATION));
1567         }
1568 
1569         PickerDbFacade.QueryFilterBuilder cloudQfb =
1570                 new PickerDbFacade.QueryFilterBuilder(/* limit */ 1000);
1571         try (Cursor cr =
1572                      mFacade.queryAlbumMediaForUi(
1573                              cloudQfb.setAlbumId(ALBUM_ID).build(), CLOUD_PROVIDER)) {
1574             assertWithMessage(
1575                     "Unexpected number of rows on queryAlbumMediaForUi with cloud provider.")
1576                     .that(cr.getCount()).isEqualTo(2);
1577             cr.moveToFirst();
1578             assertThrows(
1579                     IllegalArgumentException.class,
1580                     () -> cr.getColumnIndexOrThrow(MediaColumns.WIDTH));
1581             assertThrows(
1582                     IllegalArgumentException.class,
1583                     () -> cr.getColumnIndexOrThrow(MediaColumns.HEIGHT));
1584             assertThrows(
1585                     IllegalArgumentException.class,
1586                     () -> cr.getColumnIndexOrThrow(MediaColumns.ORIENTATION));
1587         }
1588     }
1589 
1590     @Test
testSetCloudProvider()1591     public void testSetCloudProvider() throws Exception {
1592         Cursor localCursor = getLocalMediaCursor(LOCAL_ID, DATE_TAKEN_MS);
1593         Cursor cloudCursor = getCloudMediaCursor(CLOUD_ID, null, DATE_TAKEN_MS);
1594 
1595         assertAddMediaOperation(mFacade, LOCAL_PROVIDER, localCursor, 1);
1596         assertAddMediaOperation(mFacade, CLOUD_PROVIDER, cloudCursor, 1);
1597 
1598         try (Cursor cr = queryMediaAll(mFacade)) {
1599             assertWithMessage(
1600                     "Unexpected number of rows on queryMediaAll with both local and cloud "
1601                             + "provider.")
1602                     .that(cr.getCount()).isEqualTo(2);
1603 
1604             cr.moveToFirst();
1605             assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS);
1606 
1607             cr.moveToNext();
1608             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS);
1609         }
1610 
1611         // Clearing the cloud provider hides cloud media
1612         mFacade.setCloudProvider(null);
1613 
1614         try (Cursor cr = queryMediaAll(mFacade)) {
1615             assertWithMessage(
1616                     "Unexpected number of rows on queryMediaAll after hiding cloud provider.")
1617                     .that(cr.getCount()).isEqualTo(1);
1618 
1619             cr.moveToFirst();
1620             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS);
1621         }
1622 
1623         // Setting the cloud provider unhides cloud media
1624         mFacade.setCloudProvider(CLOUD_PROVIDER);
1625 
1626         try (Cursor cr = queryMediaAll(mFacade)) {
1627             assertWithMessage(
1628                     "Unexpected number of rows on queryMediaAll after un-hiding cloud provider.")
1629                     .that(cr.getCount()).isEqualTo(2);
1630 
1631             cr.moveToFirst();
1632             assertCloudMediaCursor(cr, CLOUD_ID, DATE_TAKEN_MS);
1633 
1634             cr.moveToNext();
1635             assertCloudMediaCursor(cr, LOCAL_ID, DATE_TAKEN_MS);
1636         }
1637     }
1638 
1639     @Test
testFavorites()1640     public void testFavorites() throws Exception {
1641         Cursor localCursor1 = getMediaCursor(LOCAL_ID + "1", DATE_TAKEN_MS, GENERATION_MODIFIED,
1642                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1643                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
1644         Cursor localCursor2 = getMediaCursor(LOCAL_ID + "2", DATE_TAKEN_MS, GENERATION_MODIFIED,
1645                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1646                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1647         Cursor cloudCursor1 = getMediaCursor(CLOUD_ID + "1", DATE_TAKEN_MS, GENERATION_MODIFIED,
1648                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1649                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
1650         Cursor cloudCursor2 = getMediaCursor(CLOUD_ID + "2", DATE_TAKEN_MS, GENERATION_MODIFIED,
1651                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1652                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1653 
1654         try (PickerDbFacade.DbWriteOperation operation =
1655                      mFacade.beginAddMediaOperation(LOCAL_PROVIDER)) {
1656             assertWriteOperation(operation, localCursor1, 1);
1657             assertWriteOperation(operation, localCursor2, 1);
1658             operation.setSuccess();
1659         }
1660         try (PickerDbFacade.DbWriteOperation operation =
1661                      mFacade.beginAddMediaOperation(CLOUD_PROVIDER)) {
1662             assertWriteOperation(operation, cloudCursor1, 1);
1663             assertWriteOperation(operation, cloudCursor2, 1);
1664             operation.setSuccess();
1665         }
1666 
1667         PickerDbFacade.QueryFilterBuilder qfb =
1668                 new PickerDbFacade.QueryFilterBuilder(/* limit */ 1000);
1669         try (Cursor cr = mFacade.queryMediaForUi(qfb.build())) {
1670             assertWithMessage(
1671                     "Unexpected number of rows on queryMediaForUi with no filter.")
1672                     .that(cr.getCount()).isEqualTo(4);
1673         }
1674 
1675         qfb.setIsFavorite(true);
1676         try (Cursor cr = mFacade.queryMediaForUi(qfb.build())) {
1677             assertWithMessage(
1678                     "Unexpected number of rows on queryMediaForUi with isFavorite filter set to "
1679                             + "true.")
1680                     .that(cr.getCount()).isEqualTo(2);
1681             cr.moveToFirst();
1682             assertCloudMediaCursor(cr, CLOUD_ID + 1, DATE_TAKEN_MS);
1683 
1684             cr.moveToNext();
1685             assertCloudMediaCursor(cr, LOCAL_ID + 1, DATE_TAKEN_MS);
1686         }
1687     }
1688 
1689     @Test
testGetFavoritesAlbumWithoutFilter()1690     public void testGetFavoritesAlbumWithoutFilter() throws Exception {
1691         Cursor localCursor1 = getMediaCursor(LOCAL_ID + "1", DATE_TAKEN_MS, GENERATION_MODIFIED,
1692                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1693                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
1694         Cursor localCursor2 = getMediaCursor(LOCAL_ID + "2", DATE_TAKEN_MS, GENERATION_MODIFIED,
1695                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
1696                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1697         Cursor cloudCursor1 = getMediaCursor(CLOUD_ID + "1", DATE_TAKEN_MS, GENERATION_MODIFIED,
1698                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
1699                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
1700         Cursor cloudCursor2 = getMediaCursor(CLOUD_ID + "2", DATE_TAKEN_MS, GENERATION_MODIFIED,
1701                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1702                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1703 
1704         try (PickerDbFacade.DbWriteOperation operation =
1705                      mFacade.beginAddMediaOperation(LOCAL_PROVIDER)) {
1706             assertWriteOperation(operation, localCursor1, 1);
1707             assertWriteOperation(operation, localCursor2, 1);
1708             operation.setSuccess();
1709         }
1710         try (PickerDbFacade.DbWriteOperation operation =
1711                      mFacade.beginAddMediaOperation(CLOUD_PROVIDER)) {
1712             assertWriteOperation(operation, cloudCursor1, 1);
1713             assertWriteOperation(operation, cloudCursor2, 1);
1714             operation.setSuccess();
1715         }
1716 
1717         PickerDbFacade.QueryFilterBuilder qfb =
1718                 new PickerDbFacade.QueryFilterBuilder(/* limit */ 1000);
1719         try (Cursor cr = mFacade.queryMediaForUi(qfb.build())) {
1720             assertWithMessage("Unexpected number of rows on queryMediaForUi without any filter.")
1721                     .that(cr.getCount()).isEqualTo(4);
1722         }
1723 
1724         try (Cursor cr = mFacade.getMergedAlbums(qfb.build(), CLOUD_PROVIDER)) {
1725             assertWithMessage(
1726                     "Unexpected number of rows on getMergedAlbums without any filter for cloud "
1727                             + "provider.")
1728                     .that(cr.getCount()).isEqualTo(2);
1729             cr.moveToFirst();
1730             assertCloudAlbumCursor(cr,
1731                     ALBUM_ID_FAVORITES,
1732                     ALBUM_ID_FAVORITES,
1733                     LOCAL_ID + "1",
1734                     DATE_TAKEN_MS,
1735                     /* count */ 2);
1736             cr.moveToNext();
1737             assertCloudAlbumCursor(cr,
1738                     ALBUM_ID_VIDEOS,
1739                     ALBUM_ID_VIDEOS,
1740                     LOCAL_ID + "1",
1741                     DATE_TAKEN_MS,
1742                     /* count */ 2);
1743         }
1744     }
1745 
1746     @Test
testGetVideosAlbumWithMimeTypesFilter()1747     public void testGetVideosAlbumWithMimeTypesFilter() throws Exception {
1748         Cursor localCursor1 = getMediaCursor(LOCAL_ID + "1", DATE_TAKEN_MS, GENERATION_MODIFIED,
1749                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1750                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1751         Cursor localCursor2 = getMediaCursor(LOCAL_ID + "2", DATE_TAKEN_MS, GENERATION_MODIFIED,
1752                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
1753                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
1754         Cursor cloudCursor1 = getMediaCursor(CLOUD_ID + "1", DATE_TAKEN_MS, GENERATION_MODIFIED,
1755                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
1756                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1757         Cursor cloudCursor2 = getMediaCursor(CLOUD_ID + "2", DATE_TAKEN_MS, GENERATION_MODIFIED,
1758                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1759                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1760 
1761         try (PickerDbFacade.DbWriteOperation operation =
1762                      mFacade.beginAddMediaOperation(LOCAL_PROVIDER)) {
1763             assertWriteOperation(operation, localCursor1, 1);
1764             assertWriteOperation(operation, localCursor2, 1);
1765             operation.setSuccess();
1766         }
1767         try (PickerDbFacade.DbWriteOperation operation =
1768                      mFacade.beginAddMediaOperation(CLOUD_PROVIDER)) {
1769             assertWriteOperation(operation, cloudCursor1, 1);
1770             assertWriteOperation(operation, cloudCursor2, 1);
1771             operation.setSuccess();
1772         }
1773 
1774         PickerDbFacade.QueryFilterBuilder qfb =
1775                 new PickerDbFacade.QueryFilterBuilder(/* limit */ 1000);
1776         try (Cursor cr = mFacade.queryMediaForUi(qfb.build())) {
1777             assertWithMessage("Unexpected number of rows on queryMediaForUi without any filter.")
1778                     .that(cr.getCount()).isEqualTo(4);
1779         }
1780 
1781         try (Cursor cr = mFacade.getMergedAlbums(qfb.build(), CLOUD_PROVIDER)) {
1782             assertWithMessage(
1783                     "Unexpected number of rows on getMergedAlbums without any filter for cloud "
1784                             + "provider.")
1785                     .that(cr.getCount()).isEqualTo(2);
1786             cr.moveToFirst();
1787             assertCloudAlbumCursor(cr,
1788                     ALBUM_ID_FAVORITES,
1789                     ALBUM_ID_FAVORITES,
1790                     LOCAL_ID + "2",
1791                     DATE_TAKEN_MS,
1792                     /* count */ 1);
1793             cr.moveToNext();
1794             assertCloudAlbumCursor(cr,
1795                     ALBUM_ID_VIDEOS,
1796                     ALBUM_ID_VIDEOS,
1797                     LOCAL_ID + "1",
1798                     DATE_TAKEN_MS,
1799                     /* count */ 2);
1800         }
1801 
1802         qfb.setMimeTypes(new String[]{MP4_VIDEO_MIME_TYPE, JPEG_IMAGE_MIME_TYPE});
1803         try (Cursor cr = mFacade.getMergedAlbums(qfb.build(), /* cloudProvider*/ CLOUD_PROVIDER)) {
1804             assertWithMessage(
1805                     "Unexpected number of rows on getMergedAlbums without any filter for cloud "
1806                             + "provider.")
1807                     .that(cr.getCount()).isEqualTo(2);
1808             cr.moveToFirst();
1809             assertCloudAlbumCursor(cr,
1810                     ALBUM_ID_FAVORITES,
1811                     ALBUM_ID_FAVORITES,
1812                     LOCAL_ID + "2",
1813                     DATE_TAKEN_MS,
1814                     /* count */ 1);
1815             cr.moveToNext();
1816             assertCloudAlbumCursor(cr,
1817                     ALBUM_ID_VIDEOS,
1818                     ALBUM_ID_VIDEOS,
1819                     LOCAL_ID + "1",
1820                     DATE_TAKEN_MS,
1821                     /* count */ 2);
1822         }
1823 
1824         qfb.setMimeTypes(new String[]{GIF_IMAGE_MIME_TYPE, JPEG_IMAGE_MIME_TYPE});
1825         try (Cursor cr = mFacade.getMergedAlbums(qfb.build(), /* cloudProvider*/ CLOUD_PROVIDER)) {
1826             assertWithMessage(
1827                     "Unexpected number of rows on getMergedAlbums with mime type filter set to "
1828                             + "{GIF_IMAGE_MIME_TYPE, JPEG_IMAGE_MIME_TYPE} for cloud provider.")
1829                     .that(cr.getCount()).isEqualTo(1);
1830             cr.moveToFirst();
1831             assertCloudAlbumCursor(cr,
1832                     ALBUM_ID_FAVORITES,
1833                     ALBUM_ID_FAVORITES,
1834                     LOCAL_ID + "2",
1835                     DATE_TAKEN_MS,
1836                     /* count */ 1);
1837         }
1838     }
1839 
1840     @Test
testGetFavoritesAlbumWithMimeTypesFilter()1841     public void testGetFavoritesAlbumWithMimeTypesFilter() throws Exception {
1842         Cursor localCursor1 = getMediaCursor(LOCAL_ID + "1", DATE_TAKEN_MS, GENERATION_MODIFIED,
1843                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1844                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
1845         Cursor localCursor2 = getMediaCursor(LOCAL_ID + "2", DATE_TAKEN_MS, GENERATION_MODIFIED,
1846                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
1847                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1848         Cursor cloudCursor1 = getMediaCursor(CLOUD_ID + "1", DATE_TAKEN_MS, GENERATION_MODIFIED,
1849                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
1850                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
1851         Cursor cloudCursor2 = getMediaCursor(CLOUD_ID + "2", DATE_TAKEN_MS, GENERATION_MODIFIED,
1852                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1853                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1854 
1855         try (PickerDbFacade.DbWriteOperation operation =
1856                      mFacade.beginAddMediaOperation(LOCAL_PROVIDER)) {
1857             assertWriteOperation(operation, localCursor1, 1);
1858             assertWriteOperation(operation, localCursor2, 1);
1859             operation.setSuccess();
1860         }
1861         try (PickerDbFacade.DbWriteOperation operation =
1862                      mFacade.beginAddMediaOperation(CLOUD_PROVIDER)) {
1863             assertWriteOperation(operation, cloudCursor1, 1);
1864             assertWriteOperation(operation, cloudCursor2, 1);
1865             operation.setSuccess();
1866         }
1867 
1868         PickerDbFacade.QueryFilterBuilder qfb =
1869                 new PickerDbFacade.QueryFilterBuilder(/* limit */ 1000);
1870         try (Cursor cr = mFacade.queryMediaForUi(qfb.build())) {
1871             assertWithMessage("Unexpected number of rows on queryMediaForUi without any filter.")
1872                     .that(cr.getCount()).isEqualTo(4);
1873         }
1874 
1875         try (Cursor cr = mFacade.getMergedAlbums(qfb.build(), CLOUD_PROVIDER)) {
1876             assertWithMessage(
1877                     "Unexpected number of rows on getMergedAlbums without any filter for cloud "
1878                             + "provider.")
1879                     .that(cr.getCount()).isEqualTo(2);
1880             cr.moveToFirst();
1881             assertCloudAlbumCursor(cr,
1882                     ALBUM_ID_FAVORITES,
1883                     ALBUM_ID_FAVORITES,
1884                     LOCAL_ID + "1",
1885                     DATE_TAKEN_MS,
1886                     /* count */ 2);
1887             cr.moveToNext();
1888             assertCloudAlbumCursor(cr,
1889                     ALBUM_ID_VIDEOS,
1890                     ALBUM_ID_VIDEOS,
1891                     LOCAL_ID + "1",
1892                     DATE_TAKEN_MS,
1893                     /* count */ 2);
1894         }
1895 
1896         qfb.setMimeTypes(IMAGE_MIME_TYPES_QUERY);
1897         try (Cursor cr = mFacade.getMergedAlbums(qfb.build(), /* cloudProvider*/ null)) {
1898             assertWithMessage(
1899                     "Unexpected number of rows on getMergedAlbums with mime type filter set to "
1900                             + "IMAGE_MIME_TYPES_QUERY and cloudProvider set to null.")
1901                     .that(cr.getCount()).isEqualTo(1);
1902             cr.moveToFirst();
1903             assertCloudAlbumCursor(cr,
1904                     ALBUM_ID_FAVORITES,
1905                     ALBUM_ID_FAVORITES,
1906                     CLOUD_ID + "1",
1907                     DATE_TAKEN_MS,
1908                     /* count */ 1);
1909         }
1910 
1911         try (Cursor cr = mFacade.getMergedAlbums(qfb.build(), CLOUD_PROVIDER)) {
1912             assertWithMessage(
1913                     "Unexpected number of rows on getMergedAlbums with mime type filter set to "
1914                             + "{IMAGE_MIME_TYPES_QUERY} with cloudProvider.")
1915                     .that(cr.getCount()).isEqualTo(1);
1916             cr.moveToFirst();
1917             assertCloudAlbumCursor(cr,
1918                     ALBUM_ID_FAVORITES,
1919                     ALBUM_ID_FAVORITES,
1920                     CLOUD_ID + "1",
1921                     DATE_TAKEN_MS,
1922                     /* count */ 1);
1923         }
1924 
1925         qfb.setMimeTypes(VIDEO_MIME_TYPES_QUERY);
1926         try (Cursor cr = mFacade.getMergedAlbums(qfb.build(), CLOUD_PROVIDER)) {
1927             assertWithMessage(
1928                     "Unexpected number of rows on getMergedAlbums with mime type filter set to "
1929                             + "VIDEO_MIME_TYPES_QUERY with cloudProvider.")
1930                     .that(cr.getCount()).isEqualTo(2);
1931             cr.moveToFirst();
1932             assertCloudAlbumCursor(cr,
1933                     ALBUM_ID_FAVORITES,
1934                     ALBUM_ID_FAVORITES,
1935                     LOCAL_ID + "1",
1936                     DATE_TAKEN_MS,
1937                     /* count */ 1);
1938             cr.moveToNext();
1939             assertCloudAlbumCursor(cr,
1940                     ALBUM_ID_VIDEOS,
1941                     ALBUM_ID_VIDEOS,
1942                     LOCAL_ID + "1",
1943                     DATE_TAKEN_MS,
1944                     /* count */ 2);
1945         }
1946 
1947         qfb.setMimeTypes(new String[]{"foo"});
1948         try (Cursor cr = mFacade.getMergedAlbums(qfb.build(), CLOUD_PROVIDER)) {
1949             assertWithMessage(
1950                     "Unexpected number of rows on getMergedAlbums with mime type filter set to "
1951                             + "{\"foo\"} and not null cloudProvider.")
1952                     .that(cr.getCount()).isEqualTo(1);
1953         }
1954     }
1955 
1956     @Test
testFetchLocalOnly()1957     public void testFetchLocalOnly() throws Exception {
1958         Cursor localCursor1 = getMediaCursor(LOCAL_ID + "1", DATE_TAKEN_MS, GENERATION_MODIFIED,
1959                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
1960                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
1961         Cursor localCursor2 = getMediaCursor(LOCAL_ID + "2", DATE_TAKEN_MS, GENERATION_MODIFIED,
1962                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
1963                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1964         Cursor cloudCursor1 = getMediaCursor(CLOUD_ID + "1", DATE_TAKEN_MS, GENERATION_MODIFIED,
1965                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
1966                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
1967         Cursor cloudCursor2 = getMediaCursor(CLOUD_ID + "2", DATE_TAKEN_MS, GENERATION_MODIFIED,
1968                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
1969                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
1970         // Item Info:
1971         // 2 items - local - one of them in favorite album
1972         // 2 items - cloud - one in favorite album, one in video album
1973         // Albums Info:
1974         // Videos     - Merged Album - 1 Video File (1 cloud)
1975         // Favorites  - Merged Album - 2 files (1 local + 1 cloud)
1976 
1977         try (PickerDbFacade.DbWriteOperation operation =
1978                      mFacade.beginAddMediaOperation(CLOUD_PROVIDER)) {
1979             assertWriteOperation(operation, cloudCursor1, 1);
1980             assertWriteOperation(operation, cloudCursor2, 1);
1981             operation.setSuccess();
1982         }
1983         try (PickerDbFacade.DbWriteOperation operation =
1984                      mFacade.beginAddMediaOperation(LOCAL_PROVIDER)) {
1985             assertWriteOperation(operation, localCursor1, 1);
1986             assertWriteOperation(operation, localCursor2, 1);
1987             operation.setSuccess();
1988         }
1989 
1990         PickerDbFacade.QueryFilterBuilder qfb =
1991                 new PickerDbFacade.QueryFilterBuilder(/* limit */ 1000);
1992         // Verify that we see all(local + cloud) items.
1993         try (Cursor cr = mFacade.queryMediaForUi(qfb.build())) {
1994             assertWithMessage("Unexpected number of rows on queryMediaForUi without any filter.")
1995                     .that(cr.getCount()).isEqualTo(4);
1996         }
1997 
1998         // Verify that we only see local items with isLocalOnly=true
1999         qfb.setIsLocalOnly(true);
2000         try (Cursor cr = mFacade.queryMediaForUi(qfb.build())) {
2001             assertWithMessage(
2002                     "Unexpected number of rows on queryMediaForUi with isLocalOnly set to true.")
2003                     .that(cr.getCount()).isEqualTo(2);
2004 
2005             cr.moveToNext();
2006             assertWithMessage("Unexpected value of MediaColumns.ID at cursor.")
2007                     .that(cr.getString(cr.getColumnIndexOrThrow(MediaColumns.ID))).isEqualTo(
2008                             LOCAL_ID + "2");
2009             cr.moveToNext();
2010             assertWithMessage("Unexpected value of MediaColumns.ID at cursor.")
2011                     .that(cr.getString(cr.getColumnIndexOrThrow(MediaColumns.ID))).isEqualTo(
2012                             LOCAL_ID + "1");
2013         }
2014 
2015         // Verify that we see all available merged albums and their respective media count
2016         qfb.setIsLocalOnly(false);
2017         try (Cursor cr = mFacade.getMergedAlbums(qfb.build(), CLOUD_PROVIDER)) {
2018             assertWithMessage(
2019                     "Unexpected number of rows on getMergedAlbums with isLocalOnly set to false.")
2020                     .that(cr.getCount()).isEqualTo(2);
2021             cr.moveToFirst();
2022             assertCloudAlbumCursor(cr,
2023                     ALBUM_ID_FAVORITES,
2024                     ALBUM_ID_FAVORITES,
2025                     CLOUD_ID + "1",
2026                     DATE_TAKEN_MS,
2027                     /* count */ 2);
2028             cr.moveToNext();
2029             assertCloudAlbumCursor(cr,
2030                     ALBUM_ID_VIDEOS,
2031                     ALBUM_ID_VIDEOS,
2032                     CLOUD_ID + "2",
2033                     DATE_TAKEN_MS,
2034                     /* count */ 1);
2035         }
2036 
2037         qfb.setIsLocalOnly(true);
2038         // Verify that with isLocalOnly=true, we only see one album with only one local item.
2039         try (Cursor cr = mFacade.getMergedAlbums(qfb.build(), /* cloudProvider */ null)) {
2040             assertWithMessage(
2041                     "Unexpected number of rows on getMergedAlbums with isLocalOnly set to true "
2042                             + "and cloudProvider set to null.")
2043                     .that(cr.getCount()).isEqualTo(1);
2044             cr.moveToFirst();
2045             assertCloudAlbumCursor(cr,
2046                     ALBUM_ID_FAVORITES,
2047                     ALBUM_ID_FAVORITES,
2048                     LOCAL_ID + "1",
2049                     DATE_TAKEN_MS,
2050                     /* count */ 1);
2051         }
2052     }
2053 
2054     @Test
testFetchItems_withIdSelection()2055     public void testFetchItems_withIdSelection() {
2056         Cursor localCursor1 = getMediaCursor(LOCAL_ID + "1", DATE_TAKEN_MS, GENERATION_MODIFIED,
2057                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
2058                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
2059         Cursor localCursor2 = getMediaCursor(LOCAL_ID + "2", DATE_TAKEN_MS, GENERATION_MODIFIED,
2060                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
2061                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
2062         Cursor cloudCursor1 = getMediaCursor(CLOUD_ID + "1", DATE_TAKEN_MS, GENERATION_MODIFIED,
2063                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
2064                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
2065         Cursor cloudCursor2 = getMediaCursor(CLOUD_ID + "2", DATE_TAKEN_MS, GENERATION_MODIFIED,
2066                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
2067                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
2068         // Item Info:
2069         // 2 items - local - one of them in favorite album
2070         // 2 items - cloud - one in favorite album, one in video album
2071         // Albums Info:
2072         // Videos     - Merged Album - 1 Video File (1 cloud)
2073         // Favorites  - Merged Album - 2 files (1 local + 1 cloud)
2074 
2075         try (PickerDbFacade.DbWriteOperation operation =
2076                      mFacade.beginAddMediaOperation(CLOUD_PROVIDER)) {
2077             assertWriteOperation(operation, cloudCursor1, 1);
2078             assertWriteOperation(operation, cloudCursor2, 1);
2079             operation.setSuccess();
2080         }
2081         try (PickerDbFacade.DbWriteOperation operation =
2082                      mFacade.beginAddMediaOperation(LOCAL_PROVIDER)) {
2083             assertWriteOperation(operation, localCursor1, 1);
2084             assertWriteOperation(operation, localCursor2, 1);
2085             operation.setSuccess();
2086         }
2087 
2088         PickerDbFacade.QueryFilterBuilder qfb =
2089                 new PickerDbFacade.QueryFilterBuilder(/* limit */ 1000);
2090 
2091         // If mShouldScreenSelectionUris is set and no ids selection items are passed, an empty
2092         // cursor should be returned.
2093         qfb.setShouldScreenSelectionUris(true);
2094         try (Cursor cr = mFacade.queryMediaForUi(qfb.build())) {
2095             assertWithMessage("Unexpected number of rows on queryMediaForUi."
2096                     + "No items should have been returned.")
2097                     .that(cr.getCount()).isEqualTo(0);
2098         }
2099 
2100         // Setting one local id as an input for selection.
2101         // 1 local item should be returned.
2102         qfb.setLocalPreSelectedIds(List.of(LOCAL_ID + "2"));
2103         qfb.setCloudPreSelectionIds(null);
2104         qfb.setShouldScreenSelectionUris(false);
2105         try (Cursor cr = mFacade.queryMediaForUi(qfb.build())) {
2106             assertWithMessage("Unexpected number of rows on queryMediaForUi."
2107                     + "Expected number of items is 1.")
2108                     .that(cr.getCount()).isEqualTo(1);
2109             cr.moveToNext();
2110             assertWithMessage("Unexpected value of MediaColumns.ID at cursor.")
2111                     .that(cr.getString(cr.getColumnIndex(MediaColumns.ID))).isEqualTo(
2112                             LOCAL_ID + "2");
2113         }
2114 
2115         // Setting one cloud id as an input for selection, and disabling local only param.
2116         // 1 cloud item should be returned.
2117         qfb.setIsLocalOnly(false);
2118         qfb.setLocalPreSelectedIds(null);
2119         qfb.setCloudPreSelectionIds(List.of(CLOUD_ID + "1"));
2120         try (Cursor cr = mFacade.queryMediaForUi(qfb.build())) {
2121             assertWithMessage("Unexpected number of rows on queryMediaForUi."
2122                     + "Expected number of items is 1.")
2123                     .that(cr.getCount()).isEqualTo(1);
2124             cr.moveToNext();
2125             assertWithMessage("Unexpected value of MediaColumns.ID at cursor.")
2126                     .that(cr.getString(cr.getColumnIndex(MediaColumns.ID))).isEqualTo(
2127                             CLOUD_ID + "1");
2128         }
2129 
2130         // If local only is enabled and only cloud ids are present, then no items should be
2131         // returned.
2132         qfb.setIsLocalOnly(true);
2133         qfb.setLocalPreSelectedIds(null);
2134         qfb.setCloudPreSelectionIds(List.of(CLOUD_ID + "1"));
2135         try (Cursor cr = mFacade.queryMediaForUi(qfb.build())) {
2136             assertWithMessage("Unexpected number of rows on queryMediaForUi."
2137                     + "Expected number of items is 0.")
2138                     .that(cr.getCount()).isEqualTo(0);
2139         }
2140 
2141 
2142         // Setting one local id and one cloud id, 2 items should be returned.
2143         qfb.setIsLocalOnly(false);
2144         qfb.setLocalPreSelectedIds(List.of(LOCAL_ID + "2"));
2145         qfb.setCloudPreSelectionIds(List.of(CLOUD_ID + "1"));
2146         try (Cursor cr = mFacade.queryMediaForUi(qfb.build())) {
2147             assertWithMessage("Unexpected number of rows on queryMediaForUi."
2148                     + "Expected number of items is 2.")
2149                     .that(cr.getCount()).isEqualTo(2);
2150             cr.moveToNext();
2151             assertWithMessage("Unexpected value of MediaColumns.ID at cursor.")
2152                     .that(cr.getString(cr.getColumnIndex(MediaColumns.ID))).isEqualTo(
2153                             LOCAL_ID + "2");
2154             cr.moveToNext();
2155             assertWithMessage("Unexpected value of MediaColumns.ID at cursor.")
2156                     .that(cr.getString(cr.getColumnIndex(MediaColumns.ID))).isEqualTo(
2157                             CLOUD_ID + "1");
2158         }
2159     }
2160 
2161     @Test
testDataColumn()2162     public void testDataColumn() throws Exception {
2163         Cursor imageCursor = getMediaCursor(LOCAL_ID, DATE_TAKEN_MS, GENERATION_MODIFIED,
2164                 /* mediaStoreUri */ null, SIZE_BYTES, JPEG_IMAGE_MIME_TYPE,
2165                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
2166         Cursor videoCursor = getMediaCursor(LOCAL_ID + 1, DATE_TAKEN_MS, GENERATION_MODIFIED,
2167                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
2168                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ false);
2169 
2170         try (PickerDbFacade.DbWriteOperation operation =
2171                      mFacade.beginAddMediaOperation(LOCAL_PROVIDER)) {
2172             assertWriteOperation(operation, imageCursor, 1);
2173             assertWriteOperation(operation, videoCursor, 1);
2174             operation.setSuccess();
2175         }
2176 
2177         try (Cursor cr = queryMediaAll(mFacade)) {
2178             assertWithMessage("Unexpected number of rows on queryMediaForUi.")
2179                     .that(cr.getCount()).isEqualTo(2);
2180             cr.moveToFirst();
2181             assertCloudMediaCursor(cr, LOCAL_ID + 1, MP4_VIDEO_MIME_TYPE);
2182 
2183             cr.moveToNext();
2184             assertCloudMediaCursor(cr, LOCAL_ID, JPEG_IMAGE_MIME_TYPE);
2185         }
2186     }
2187 
2188     @Test
testAddMediaFailure()2189     public void testAddMediaFailure() throws Exception {
2190         try (PickerDbFacade.DbWriteOperation operation =
2191                      mFacade.beginAddMediaOperation(CLOUD_PROVIDER)) {
2192             assertThrows(Exception.class, () -> operation.execute(null /* cursor */));
2193         }
2194     }
2195 
2196     @Test
testRemoveMediaFailure()2197     public void testRemoveMediaFailure() throws Exception {
2198         try (PickerDbFacade.DbWriteOperation operation =
2199                      mFacade.beginRemoveMediaOperation(CLOUD_PROVIDER)) {
2200             assertThrows(Exception.class, () -> operation.execute(null /* cursor */));
2201         }
2202     }
2203 
2204     @Test
testUpdateMediaSuccess()2205     public void testUpdateMediaSuccess() throws Exception {
2206         Cursor localCursor = getMediaCursor(LOCAL_ID, DATE_TAKEN_MS, GENERATION_MODIFIED,
2207                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
2208                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
2209         try (PickerDbFacade.DbWriteOperation operation =
2210                      mFacade.beginAddMediaOperation(LOCAL_PROVIDER)) {
2211             operation.execute(localCursor);
2212             operation.setSuccess();
2213         }
2214 
2215         try (PickerDbFacade.UpdateMediaOperation operation =
2216                      mFacade.beginUpdateMediaOperation(LOCAL_PROVIDER)) {
2217             ContentValues values = new ContentValues();
2218             values.put(PickerDbFacade.KEY_STANDARD_MIME_TYPE_EXTENSION,
2219                     MediaColumns.STANDARD_MIME_TYPE_EXTENSION_ANIMATED_WEBP);
2220             assertWithMessage("Failed to update media with LOCAL_ID.")
2221                     .that(operation.execute(LOCAL_ID, values)).isTrue();
2222             operation.setSuccess();
2223         }
2224 
2225         try (Cursor cursor = queryMediaAll(mFacade)) {
2226             assertWithMessage("Unexpected number of rows after update operation.")
2227                     .that(cursor.getCount()).isEqualTo(1);
2228 
2229             // Assert that STANDARD_MIME_TYPE_EXTENSION has been updated
2230             cursor.moveToFirst();
2231             assertWithMessage("Failed to update STANDARD_MIME_TYPE_EXTENSION.")
2232                     .that(cursor.getInt(cursor.getColumnIndexOrThrow(
2233                             MediaColumns.STANDARD_MIME_TYPE_EXTENSION)))
2234                     .isEqualTo(MediaColumns.STANDARD_MIME_TYPE_EXTENSION_ANIMATED_WEBP);
2235         }
2236     }
2237 
2238     @Test
testUpdateMediaFailure()2239     public void testUpdateMediaFailure() throws Exception {
2240         Cursor localCursor = getMediaCursor(LOCAL_ID, DATE_TAKEN_MS, GENERATION_MODIFIED,
2241                 /* mediaStoreUri */ null, SIZE_BYTES, MP4_VIDEO_MIME_TYPE,
2242                 STANDARD_MIME_TYPE_EXTENSION, /* isFavorite */ true);
2243         try (PickerDbFacade.DbWriteOperation operation =
2244                      mFacade.beginAddMediaOperation(LOCAL_PROVIDER)) {
2245             operation.execute(localCursor);
2246             operation.setSuccess();
2247         }
2248 
2249         try (PickerDbFacade.UpdateMediaOperation operation =
2250                      mFacade.beginUpdateMediaOperation(LOCAL_PROVIDER)) {
2251             ContentValues values = new ContentValues();
2252             values.put(PickerDbFacade.KEY_STANDARD_MIME_TYPE_EXTENSION,
2253                     MediaColumns.STANDARD_MIME_TYPE_EXTENSION_ANIMATED_WEBP);
2254             assertWithMessage("Unexpected, should have failed to update media with CLOUD_ID.")
2255                     .that(operation.execute(CLOUD_ID, values)).isFalse();
2256             operation.setSuccess();
2257         }
2258 
2259         try (Cursor cursor = queryMediaAll(mFacade)) {
2260             assertWithMessage("Unexpected number of rows after update operation.")
2261                     .that(cursor.getCount()).isEqualTo(1);
2262 
2263             // Assert that STANDARD_MIME_TYPE_EXTENSION is same as before
2264             cursor.moveToFirst();
2265             assertWithMessage("Unexpected STANDARD_MIME_TYPE_EXTENSION, not same as before.")
2266                     .that(cursor.getInt(cursor.getColumnIndexOrThrow(
2267                             MediaColumns.STANDARD_MIME_TYPE_EXTENSION)))
2268                     .isEqualTo(STANDARD_MIME_TYPE_EXTENSION);
2269         }
2270     }
2271 }
2272