• 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;
18 
19 import static android.provider.CloudMediaProviderContract.AlbumColumns;
20 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_CAMERA;
21 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_DOWNLOADS;
22 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_FAVORITES;
23 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_SCREENSHOTS;
24 import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS;
25 import static android.provider.CloudMediaProviderContract.MediaColumns;
26 import static android.provider.MediaStore.VOLUME_EXTERNAL;
27 
28 import static com.android.providers.media.photopicker.PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY;
29 import static com.android.providers.media.util.MimeUtils.isImageMimeType;
30 import static com.android.providers.media.util.MimeUtils.isVideoMimeType;
31 
32 import static com.google.common.truth.Truth.assertThat;
33 import static com.google.common.truth.Truth.assertWithMessage;
34 
35 import android.Manifest;
36 import android.app.Instrumentation;
37 import android.app.UiAutomation;
38 import android.content.ContentResolver;
39 import android.content.ContentValues;
40 import android.content.Context;
41 import android.content.res.AssetFileDescriptor;
42 import android.database.Cursor;
43 import android.net.Uri;
44 import android.os.Bundle;
45 import android.os.Environment;
46 import android.os.ParcelFileDescriptor;
47 import android.provider.CloudMediaProviderContract;
48 import android.provider.MediaStore;
49 import android.util.Log;
50 import android.util.Pair;
51 
52 import androidx.test.InstrumentationRegistry;
53 
54 import com.android.providers.media.IsolatedContext;
55 import com.android.providers.media.PickerProviderMediaGenerator;
56 import com.android.providers.media.PickerProviderMediaGenerator.MediaGenerator;
57 import com.android.providers.media.TestConfigStore;
58 import com.android.providers.media.cloudproviders.CloudProviderPrimary;
59 import com.android.providers.media.photopicker.data.ItemsProvider;
60 import com.android.providers.media.photopicker.data.model.Category;
61 import com.android.providers.media.photopicker.data.model.UserId;
62 
63 import com.google.common.io.ByteStreams;
64 
65 import org.junit.After;
66 import org.junit.Before;
67 import org.junit.Test;
68 
69 import java.io.File;
70 import java.io.FileInputStream;
71 import java.io.FileOutputStream;
72 import java.io.IOException;
73 import java.io.InterruptedIOException;
74 import java.util.ArrayList;
75 import java.util.Arrays;
76 import java.util.List;
77 import java.util.function.Consumer;
78 
79 public class ItemsProviderTest {
80     /**
81      * To help avoid flaky tests, give ourselves a unique nonce to be used for
82      * all filesystem paths, so that we don't risk conflicting with previous
83      * test runs.
84      */
85     private static final String NONCE = String.valueOf(System.nanoTime());
86     private static final String TAG = "ItemsProviderTest";
87     private static final String VIDEO_FILE_NAME = TAG + "_file_" + NONCE + ".mp4";
88     private static final String IMAGE_FILE_NAME = TAG + "_file_" + NONCE + ".jpg";
89     private static final String HIDDEN_DIR_NAME = TAG + "_hidden_dir_" + NONCE;
90 
91     private static final Instrumentation sInstrumentation =
92             InstrumentationRegistry.getInstrumentation();
93     private static final Context sTargetContext = sInstrumentation.getTargetContext();
94     // We are in a self-instrumentation test - MediaProviderTests - so "target" package name and
95     // "own" package are the same: com.android.providers.media.tests.
96     private static final String sTargetPackageName = sTargetContext.getPackageName();
97     private ContentResolver mIsolatedResolver;
98     private ItemsProvider mItemsProvider;
99     private TestConfigStore mConfigStore;
100 
101     @Before
setUp()102     public void setUp() throws Exception {
103         final UiAutomation uiAutomation = sInstrumentation.getUiAutomation();
104         uiAutomation.adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
105                         Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
106                         Manifest.permission.READ_DEVICE_CONFIG,
107                         Manifest.permission.INTERACT_ACROSS_USERS);
108 
109         mConfigStore = new TestConfigStore();
110         // Remove sync delay to avoid flaky tests
111         mConfigStore.setPickerSyncDelayMs(0);
112 
113         final Context isolatedContext = new IsolatedContext(sTargetContext, /* tag */ "databases",
114                 /* asFuseThread */ false, sTargetContext.getUser(), mConfigStore);
115         mIsolatedResolver = isolatedContext.getContentResolver();
116         mItemsProvider = new ItemsProvider(isolatedContext);
117 
118         // Wait for MediaStore to be Idle to reduce flakes caused by database updates
119         MediaStore.waitForIdle(mIsolatedResolver);
120     }
121 
122     @After
tearDown()123     public void tearDown() throws Exception {
124         setCloudProvider(null);
125     }
126 
127     /**
128      * Tests {@link ItemsProvider#getAllCategories(String[], UserId)} to return correct info
129      * about {@link #ALBUM_ID_CAMERA}.
130      */
131     @Test
testGetCategories_camera()132     public void testGetCategories_camera() throws Exception {
133         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
134         assertThat(c.getCount()).isEqualTo(0);
135 
136         // Create 1 image file in Camera dir to test
137         final File cameraDir = getCameraDir();
138         File imageFile = assertCreateNewImage(cameraDir);
139         try {
140             assertGetCategoriesMatchSingle(ALBUM_ID_CAMERA, /* numberOfItems */ 1);
141         } finally {
142             imageFile.delete();
143         }
144     }
145 
146     /**
147      * Tests {@link ItemsProvider#getAllCategories(String[], UserId)} to return correct info
148      * about {@link #ALBUM_ID_CAMERA}.
149      */
150     @Test
testGetCategories_not_camera()151     public void testGetCategories_not_camera() throws Exception {
152         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
153         assertThat(c.getCount()).isEqualTo(0);
154 
155         // negative test case: image file which should not be returned in Camera category
156         final File picturesDir = getPicturesDir();
157         File nonCameraImageFile = assertCreateNewImage(picturesDir);
158         try {
159             assertGetCategoriesMatchSingle(ALBUM_ID_CAMERA, /* numberOfItems */ 0);
160         } finally {
161             nonCameraImageFile.delete();
162         }
163     }
164 
165     /**
166      * Tests {@link ItemsProvider#getAllCategories(String[], UserId)} to return correct info
167      * about {@link #ALBUM_ID_VIDEOS}.
168      */
169     @Test
testGetCategories_videos()170     public void testGetCategories_videos() throws Exception {
171         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
172         assertThat(c.getCount()).isEqualTo(0);
173 
174         // Create 1 video file in Movies dir to test
175         final File moviesDir = getMoviesDir();
176         File videoFile = assertCreateNewVideo(moviesDir);
177         try {
178             assertGetCategoriesMatchSingle(ALBUM_ID_VIDEOS, /* numberOfItems */ 1);
179         } finally {
180             videoFile.delete();
181         }
182     }
183 
184     /**
185      * Tests {@link ItemsProvider#getAllCategories(String[], UserId)} to return correct info
186      * about {@link #ALBUM_ID_VIDEOS}.
187      */
188     @Test
testGetCategories_not_videos()189     public void testGetCategories_not_videos() throws Exception {
190         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
191         assertThat(c.getCount()).isEqualTo(0);
192 
193         // negative test case: image file which should not be returned in Videos category
194         final File picturesDir = getPicturesDir();
195         File imageFile = assertCreateNewImage(picturesDir);
196         try {
197             assertGetCategoriesMatchSingle(ALBUM_ID_VIDEOS, /* numberOfItems */ 0);
198         } finally {
199             imageFile.delete();
200         }
201     }
202 
203     /**
204      * Tests {@link ItemsProvider#getAllCategories(String[], UserId)} to return correct info
205      * about {@link #ALBUM_ID_SCREENSHOTS}.
206      */
207     @Test
testGetCategories_screenshots()208     public void testGetCategories_screenshots() throws Exception {
209         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
210         assertThat(c.getCount()).isEqualTo(0);
211 
212         // Create 1 image file in Screenshots dir to test
213         final File screenshotsDir = getScreenshotsDir();
214         File imageFile = assertCreateNewImage(screenshotsDir);
215         // Create 1 image file in Screenshots dir of Downloads dir
216         final File screenshotsDirInDownloadsDir = getScreenshotsDirFromDownloadsDir();
217         File imageFileInScreenshotDirInDownloads =
218                 assertCreateNewImage(screenshotsDirInDownloadsDir);
219 
220         // Add a top level /Screenshots directory and add a test image inside of it.
221         createTestScreenshotImages();
222 
223         // This file should not be included since it's not a valid screenshot directory, even though
224         // it looks like one.
225         final File myAlbumScreenshotsDir =
226                 new File(getPicturesDir(), "MyAlbum" + Environment.DIRECTORY_SCREENSHOTS);
227         final File myAlbumScreenshotsImg = assertCreateNewImage(myAlbumScreenshotsDir);
228 
229         try {
230             assertGetCategoriesMatchMultiple(Arrays.asList(
231                     Pair.create(ALBUM_ID_SCREENSHOTS, 3),
232                     Pair.create(ALBUM_ID_DOWNLOADS, 1)
233             ));
234         } finally {
235             imageFile.delete();
236             imageFileInScreenshotDirInDownloads.delete();
237             myAlbumScreenshotsImg.delete();
238             myAlbumScreenshotsDir.delete();
239             deleteTopLevelScreenshotDir();
240         }
241     }
242 
243     /**
244      * Tests {@link ItemsProvider#getAllCategories(String[], UserId)} to return correct info
245      * about {@link #ALBUM_ID_SCREENSHOTS}.
246      */
247     @Test
testGetCategories_not_screenshots()248     public void testGetCategories_not_screenshots() throws Exception {
249         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
250         assertThat(c.getCount()).isEqualTo(0);
251 
252         // negative test case: image file which should not be returned in Screenshots category
253         final File cameraDir = getCameraDir();
254         File imageFile = assertCreateNewImage(cameraDir);
255         try {
256             assertGetCategoriesMatchSingle(ALBUM_ID_SCREENSHOTS, /* numberOfItems */ 0);
257         } finally {
258             imageFile.delete();
259         }
260     }
261 
262     /**
263      * Tests {@link ItemsProvider#getAllCategories(String[], UserId)} to return correct info
264      * about {@link AlbumColumns#ALBUM_ID_FAVORITES}.
265      */
266     @Test
testGetCategories_favorites()267     public void testGetCategories_favorites() throws Exception {
268         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
269         assertThat(c.getCount()).isEqualTo(0);
270 
271         // positive test case: image file which should be returned in favorites category
272         final File picturesDir = getPicturesDir();
273         final File imageFile = assertCreateNewImage(picturesDir);
274         setIsFavorite(imageFile);
275         try {
276             assertGetCategoriesMatchSingle(ALBUM_ID_FAVORITES, /* numberOfItems */1);
277         } finally {
278             imageFile.delete();
279         }
280     }
281 
282     /**
283      * Tests {@link ItemsProvider#getAllCategories(String[], UserId)} to return correct info
284      * about {@link AlbumColumns#ALBUM_ID_FAVORITES}.
285      */
286     @Test
testGetCategories_not_favorites()287     public void testGetCategories_not_favorites() throws Exception {
288         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
289         assertThat(c.getCount()).isEqualTo(0);
290 
291         // negative test case: image file which should not be returned in favorites category
292         final File picturesDir = getPicturesDir();
293         final File nonFavImageFile = assertCreateNewImage(picturesDir);
294         try {
295             assertGetCategoriesMatchSingle(ALBUM_ID_FAVORITES, /* numberOfItems */ 0);
296         } finally {
297             nonFavImageFile.delete();
298         }
299     }
300 
301     /**
302      * Tests {@link ItemsProvider#getAllCategories(String[], UserId)} to return correct info
303      * about {@link #ALBUM_ID_DOWNLOADS}.
304      */
305     @Test
testGetCategories_downloads()306     public void testGetCategories_downloads() throws Exception {
307         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
308         assertThat(c.getCount()).isEqualTo(0);
309 
310         // Create 1 image file in Downloads dir to test
311         final File downloadsDir = getDownloadsDir();
312         final File imageFile = assertCreateNewImage(downloadsDir);
313         try {
314             assertGetCategoriesMatchSingle(ALBUM_ID_DOWNLOADS, /* numberOfItems */ 1);
315         } finally {
316             imageFile.delete();
317         }
318     }
319 
320     /**
321      * Tests {@link ItemsProvider#getAllCategories(String[], UserId)} to return correct info
322      * about {@link #ALBUM_ID_DOWNLOADS}.
323      */
324     @Test
testGetCategories_not_downloads()325     public void testGetCategories_not_downloads() throws Exception {
326         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
327         assertThat(c.getCount()).isEqualTo(0);
328 
329         // negative test case: image file which should not be returned in Downloads category
330         final File picturesDir = getPicturesDir();
331         final File nonDownloadsImageFile = assertCreateNewImage(picturesDir);
332         try {
333             assertGetCategoriesMatchSingle(ALBUM_ID_DOWNLOADS, /* numberOfItems */ 0);
334         } finally {
335             nonDownloadsImageFile.delete();
336         }
337     }
338 
339     /**
340      * Tests {@link ItemsProvider#getAllCategories(String[], UserId)} to return correct info
341      * about {@link #ALBUM_ID_VIDEOS}.
342      */
343     @Test
testGetCategories_camera_and_videos()344     public void testGetCategories_camera_and_videos() throws Exception {
345         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
346         assertThat(c.getCount()).isEqualTo(0);
347 
348         // Create 1 video file in Camera dir to test
349         final File cameraDir = getCameraDir();
350         File videoFile = assertCreateNewVideo(cameraDir);
351         try {
352             assertGetCategoriesMatchMultiple(Arrays.asList(
353                     Pair.create(ALBUM_ID_VIDEOS, 1),
354                     Pair.create(ALBUM_ID_CAMERA, 1)
355             ));
356         } finally {
357             videoFile.delete();
358         }
359     }
360 
361     /**
362      * Tests {@link ItemsProvider#getAllCategories(String[], UserId)} to return correct info
363      * about {@link AlbumColumns#ALBUM_ID_FAVORITES}.
364      */
365     @Test
testGetCategories_screenshots_and_favorites()366     public void testGetCategories_screenshots_and_favorites() throws Exception {
367         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
368         assertThat(c.getCount()).isEqualTo(0);
369 
370         // Create 1 image file in Screenshots dir to test
371         final File screenshotsDir = getScreenshotsDir();
372         File imageFile = assertCreateNewImage(screenshotsDir);
373         setIsFavorite(imageFile);
374         try {
375             assertGetCategoriesMatchMultiple(Arrays.asList(
376                     Pair.create(ALBUM_ID_FAVORITES, 1),
377                     Pair.create(ALBUM_ID_SCREENSHOTS, 1)
378             ));
379         } finally {
380             imageFile.delete();
381         }
382     }
383 
384     /**
385      * Tests {@link ItemsProvider#getAllCategories(String[], UserId)} to return correct info
386      * about {@link AlbumColumns#ALBUM_ID_DOWNLOADS} and {@link AlbumColumns#ALBUM_ID_FAVORITES}.
387      */
388     @Test
testGetCategories_downloads_and_favorites()389     public void testGetCategories_downloads_and_favorites() throws Exception {
390         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
391         assertThat(c.getCount()).isEqualTo(0);
392 
393         // Create 1 image file in Screenshots dir to test
394         final File downloadsDir = getDownloadsDir();
395         File imageFile = assertCreateNewImage(downloadsDir);
396         setIsFavorite(imageFile);
397         try {
398             assertGetCategoriesMatchMultiple(Arrays.asList(
399                     Pair.create(ALBUM_ID_FAVORITES, 1),
400                     Pair.create(ALBUM_ID_DOWNLOADS, 1)
401             ));
402         } finally {
403             imageFile.delete();
404         }
405     }
406 
407     /**
408      * Tests {@link ItemsProvider#getAllItems(Category, int, String[], UserId)} to return all
409      * images and videos.
410      */
411     @Test
testGetItems()412     public void testGetItems() throws Exception {
413         // Create 1 image and 1 video file to test
414         // Both files should be returned.
415         File imageFile = assertCreateNewImage();
416         File videoFile = assertCreateNewVideo();
417         try {
418             final Cursor res = mItemsProvider.getAllItems(Category.DEFAULT, /* limit */ -1,
419                     /* mimeType */ null, /* userId */ null);
420             assertThat(res).isNotNull();
421             assertThat(res.getCount()).isEqualTo(2);
422 
423             assertThatOnlyImagesVideos(res);
424             // Reset the cursor back. Cursor#moveToPosition(-1) will reset the position to -1,
425             // but since there is no such valid cursor position, it returns false.
426             assertThat(res.moveToPosition(-1)).isFalse();
427             assertThatAllImagesVideos(res.getCount());
428         } finally {
429             imageFile.delete();
430             videoFile.delete();
431         }
432     }
433 
434     @Test
testGetItems_sortOrder()435     public void testGetItems_sortOrder() throws Exception {
436         try {
437             final long timeNow = System.nanoTime() / 1000;
438             final Uri imageFileDateNowPlus1Uri = prepareFileAndGetUri(
439                     new File(getDownloadsDir(),  "latest_" + IMAGE_FILE_NAME), timeNow + 1000);
440             final Uri imageFileDateNowUri
441                     = prepareFileAndGetUri(new File(getDcimDir(), IMAGE_FILE_NAME), timeNow);
442             final Uri videoFileDateNowUri
443                     = prepareFileAndGetUri(new File(getCameraDir(), VIDEO_FILE_NAME), timeNow);
444 
445             // This is the list of uris based on the expected sort order of items returned by
446             // ItemsProvider#getAllItems
447             List<Uri> uris = new ArrayList<>();
448             // This is the latest image file
449             uris.add(imageFileDateNowPlus1Uri);
450             // Video file was scanned after image file, hence has higher _id than image file
451             uris.add(videoFileDateNowUri);
452             uris.add(imageFileDateNowUri);
453 
454             try (Cursor cursor = mItemsProvider.getAllItems(Category.DEFAULT, /* limit */ -1,
455                     /* mimeType */ null, /* userId */ null)) {
456                 assertThat(cursor).isNotNull();
457 
458                 final int expectedCount = uris.size();
459                 assertThat(cursor.getCount()).isEqualTo(expectedCount);
460 
461                 int rowNum = 0;
462                 assertThat(cursor.moveToFirst()).isTrue();
463                 final int idColumnIndex = cursor.getColumnIndexOrThrow(MediaColumns.ID);
464                 while (rowNum < expectedCount) {
465                     assertWithMessage("id at row:" + rowNum + " is expected to be"
466                             + " same as id in " + uris.get(rowNum))
467                             .that(String.valueOf(cursor.getLong(idColumnIndex)))
468                             .isEqualTo(uris.get(rowNum).getLastPathSegment());
469                     cursor.moveToNext();
470                     rowNum++;
471                 }
472             }
473         } finally {
474             deleteAllFilesNoThrow();
475         }
476     }
477 
478     /**
479      * Tests {@link {@link ItemsProvider#getAllItems(Category, int, String[], UserId)}} does not
480      * return hidden images/videos.
481      */
482     @Test
testGetItems_nonMedia()483     public void testGetItems_nonMedia() throws Exception {
484         // Create 1 image and 1 video file in a hidden dir to test
485         // Both should not be returned.
486         File hiddenDir = createHiddenDir();
487         File imageFileHidden = assertCreateNewImage(hiddenDir);
488         File videoFileHidden = assertCreateNewVideo(hiddenDir);
489         try {
490             final Cursor res = mItemsProvider.getAllItems(Category.DEFAULT, /* limit */ -1,
491                     /* mimeType */ null, /* userId */ null);
492             assertThat(res).isNotNull();
493             assertThat(res.getCount()).isEqualTo(0);
494         } finally {
495             imageFileHidden.delete();
496             videoFileHidden.delete();
497             hiddenDir.delete();
498         }
499     }
500 
501     /**
502      * Tests {@link ItemsProvider#getAllItems(Category, int, String[], UserId)} to return all
503      * images and videos based on the mimeType. Image mimeType should only return images.
504      */
505     @Test
testGetItemsImages()506     public void testGetItemsImages() throws Exception {
507         // Create 1 image and 1 video file to test
508         // Only 1 should be returned.
509         File imageFile = assertCreateNewImage();
510         File videoFile = assertCreateNewVideo();
511         try {
512             final Cursor res = mItemsProvider.getAllItems(Category.DEFAULT, /* limit */ -1,
513                     /* mimeType */ new String[]{ "image/*"}, /* userId */ null);
514             assertThat(res).isNotNull();
515             assertThat(res.getCount()).isEqualTo(1);
516 
517             assertThatOnlyImages(res);
518             assertThatAllImages(res.getCount());
519         } finally {
520             imageFile.delete();
521             videoFile.delete();
522         }
523     }
524 
525     /**
526      * Tests {@link ItemsProvider#getAllItems(Category, int, String[], UserId)} to return all
527      * images and videos based on the mimeType. Image mimeType should only return images.
528      */
529     @Test
testGetItemsImages_png()530     public void testGetItemsImages_png() throws Exception {
531         // Create a jpg file image. Tests negative use case, this should not be returned below.
532         File imageFile = assertCreateNewImage();
533         try {
534             final Cursor res = mItemsProvider.getAllItems(Category.DEFAULT, /* limit */ -1,
535                     /* mimeType */ new String[]{"image/png"}, /* userId */ null);
536             assertThat(res).isNotNull();
537             assertThat(res.getCount()).isEqualTo(0);
538         } finally {
539             imageFile.delete();
540         }
541     }
542 
543     /**
544      * Tests {@link ItemsProvider#getAllItems(Category, int, String[], UserId)} does not return
545      * hidden images/videos.
546      */
547     @Test
testGetItemsImages_nonMedia()548     public void testGetItemsImages_nonMedia() throws Exception {
549         // Create 1 image and 1 video file in a hidden dir to test
550         // Both should not be returned.
551         File hiddenDir = createHiddenDir();
552         File imageFileHidden = assertCreateNewImage(hiddenDir);
553         File videoFileHidden = assertCreateNewVideo(hiddenDir);
554         try {
555             final Cursor res = mItemsProvider.getAllItems(Category.DEFAULT, /* limit */ -1,
556                     /* mimeType */ new String[]{"image/*"}, /* userId */ null);
557             assertThat(res).isNotNull();
558             assertThat(res.getCount()).isEqualTo(0);
559         } finally {
560             imageFileHidden.delete();
561             videoFileHidden.delete();
562             hiddenDir.delete();
563         }
564     }
565 
566     /**
567      * Tests {@link ItemsProvider#getAllItems(Category, int, String[], UserId)} to return all
568      * images and videos based on the mimeType. Video mimeType should only return videos.
569      */
570     @Test
testGetItemsVideos()571     public void testGetItemsVideos() throws Exception {
572         // Create 1 image and 1 video file to test
573         // Only 1 should be returned.
574         File imageFile = assertCreateNewImage();
575         File videoFile = assertCreateNewVideo();
576         try {
577             final Cursor res = mItemsProvider.getAllItems(Category.DEFAULT, /* limit */ -1,
578                     /* mimeType */ new String[]{"video/*"}, /* userId */ null);
579             assertThat(res).isNotNull();
580             assertThat(res.getCount()).isEqualTo(1);
581 
582             assertThatOnlyVideos(res);
583             assertThatAllVideos(res.getCount());
584         } finally {
585             imageFile.delete();
586             videoFile.delete();
587         }
588     }
589 
590     /**
591      * Tests {@link ItemsProvider#getAllItems(Category, int, String[], UserId)} to return all
592      * images and videos based on the mimeType. Image mimeType should only return images.
593      */
594     @Test
testGetItemsVideos_mp4()595     public void testGetItemsVideos_mp4() throws Exception {
596         // Create a mp4 video file. Tests positive use case, this should be returned below.
597         File videoFile = assertCreateNewVideo();
598         try {
599             final Cursor res = mItemsProvider.getAllItems(Category.DEFAULT, /* limit */ -1,
600                     /* mimeType */ new String[]{"video/mp4"}, /* userId */ null);
601             assertThat(res).isNotNull();
602             assertThat(res.getCount()).isEqualTo(1);
603         } finally {
604             videoFile.delete();
605         }
606     }
607 
608     /**
609      * Tests {@link ItemsProvider#getAllItems(Category, int, String[], UserId)} does not return
610      * hidden images/videos.
611      */
612     @Test
testGetItemsVideos_nonMedia()613     public void testGetItemsVideos_nonMedia() throws Exception {
614         // Create 1 image and 1 video file in a hidden dir to test the API.
615         // Both should not be returned.
616         File hiddenDir = createHiddenDir();
617         File imageFileHidden = assertCreateNewImage(hiddenDir);
618         File videoFileHidden = assertCreateNewVideo(hiddenDir);
619         try {
620             final Cursor res = mItemsProvider.getAllItems(Category.DEFAULT, /* limit */ -1,
621                     /* mimeType */ new String[]{"video/*"}, /* userId */ null);
622 
623             assertThat(res).isNotNull();
624             assertThat(res.getCount()).isEqualTo(0);
625         } finally {
626             imageFileHidden.delete();
627             videoFileHidden.delete();
628             hiddenDir.delete();
629         }
630     }
631 
632     /**
633      * Tests {@link ItemsProvider#getLocalItems(Category, int, String[], UserId)} to returns only
634      * local content.
635      */
636     @Test
testGetLocalItems_withCloud()637     public void testGetLocalItems_withCloud() throws Exception {
638         File videoFile = assertCreateNewVideo();
639         try {
640             mConfigStore.enableCloudMediaFeatureAndSetAllowedCloudProviderPackages(
641                     sTargetPackageName);
642             // Init cloud provider and add one item
643             setupCloudProvider((cloudMediaGenerator) -> {
644                 cloudMediaGenerator.addMedia(null, "cloud_id1");
645             });
646 
647             // Verify that getLocalItems includes only local contents
648             try (Cursor c = mItemsProvider.getLocalItems(Category.DEFAULT, -1, new String[]{},
649                     UserId.CURRENT_USER)) {
650                 assertThat(c.getCount()).isEqualTo(1);
651 
652                 assertThat(c.moveToFirst()).isTrue();
653                 assertThat(c.getString(c.getColumnIndexOrThrow(MediaColumns.AUTHORITY)))
654                         .isEqualTo(LOCAL_PICKER_PROVIDER_AUTHORITY);
655             }
656 
657             // Verify that getAllItems includes cloud items
658             try (Cursor c = mItemsProvider.getAllItems(Category.DEFAULT, -1, new String[]{},
659                     UserId.CURRENT_USER)) {
660                 assertThat(c.getCount()).isEqualTo(2);
661 
662                 // Verify that the first item is cloud item
663                 assertThat(c.moveToFirst()).isTrue();
664                 assertThat(c.getString(c.getColumnIndexOrThrow(MediaColumns.AUTHORITY)))
665                         .isEqualTo(CloudProviderPrimary.AUTHORITY);
666                 assertThat(c.getString(c.getColumnIndexOrThrow(MediaColumns.ID))).isEqualTo(
667                         "cloud_id1");
668 
669                 // Verify that the second item is local item
670                 assertThat(c.moveToNext()).isTrue();
671                 assertThat(c.getString(c.getColumnIndexOrThrow(MediaColumns.AUTHORITY)))
672                         .isEqualTo(LOCAL_PICKER_PROVIDER_AUTHORITY);
673             }
674         } finally {
675             videoFile.delete();
676             setCloudProvider(null);
677             mConfigStore.clearAllowedCloudProviderPackagesAndDisableCloudMediaFeature();
678         }
679     }
680 
681     @Test
testGetLocalItems_mergedAlbum_withCloud()682     public void testGetLocalItems_mergedAlbum_withCloud() throws Exception {
683         File videoFile = assertCreateNewVideo();
684         Category videoAlbum = new Category(CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS,
685                 LOCAL_PICKER_PROVIDER_AUTHORITY, "", null, 10, true);
686         try {
687             mConfigStore.enableCloudMediaFeatureAndSetAllowedCloudProviderPackages(
688                     sTargetPackageName);
689             // Init cloud provider and add one item
690             setupCloudProvider((cloudMediaGenerator) -> {
691                 cloudMediaGenerator.addMedia(null, "cloud_id1", null, "video/mp4", 0, 1024, false);
692             });
693 
694             // Verify that getLocalItems for merged album "Video" includes only local contents
695             try (Cursor c = mItemsProvider.getLocalItems(videoAlbum, -1, new String[]{},
696                     UserId.CURRENT_USER)) {
697                 assertThat(c.getCount()).isEqualTo(1);
698                 assertThat(c.moveToFirst()).isTrue();
699                 assertThat(c.getString(c.getColumnIndexOrThrow(MediaColumns.AUTHORITY)))
700                         .isEqualTo(LOCAL_PICKER_PROVIDER_AUTHORITY);
701             }
702 
703             // Verify that getAllItems for merged album "Video" also includes cloud contents
704             try (Cursor c = mItemsProvider.getAllItems(videoAlbum, -1, new String[]{},
705                     UserId.CURRENT_USER)) {
706                 assertThat(c.getCount()).isEqualTo(2);
707                 // Verify that the first item is cloud item
708                 assertThat(c.moveToFirst()).isTrue();
709                 assertThat(c.getString(c.getColumnIndexOrThrow(MediaColumns.AUTHORITY)))
710                         .isEqualTo(CloudProviderPrimary.AUTHORITY);
711                 assertThat(c.getString(c.getColumnIndexOrThrow(MediaColumns.ID)))
712                         .isEqualTo("cloud_id1");
713 
714                 // Verify that the second item is local item
715                 assertThat(c.moveToNext()).isTrue();
716                 assertThat(c.getString(c.getColumnIndexOrThrow(MediaColumns.AUTHORITY)))
717                         .isEqualTo(LOCAL_PICKER_PROVIDER_AUTHORITY);
718             }
719         } finally {
720             videoFile.delete();
721             setCloudProvider(null);
722             mConfigStore.clearAllowedCloudProviderPackagesAndDisableCloudMediaFeature();
723         }
724     }
725 
726     @Test
testGetLocalCategories_withCloud()727     public void testGetLocalCategories_withCloud() throws Exception {
728         File videoFile = assertCreateNewVideo(getMoviesDir());
729         File screenshotFile = assertCreateNewImage(getScreenshotsDir());
730         final String cloudAlbum = "testAlbum";
731 
732         try {
733             mConfigStore.enableCloudMediaFeatureAndSetAllowedCloudProviderPackages(
734                     sTargetPackageName);
735             // Init cloud provider with 2 items and one cloud album
736             setupCloudProvider((cloudMediaGenerator) -> {
737                 cloudMediaGenerator.addMedia(null, "cloud_id1", null, "video/mp4", 0, 1024, false);
738                 cloudMediaGenerator.addMedia(null, "cloud_id2", cloudAlbum, "image/jpeg", 0, 1024,
739                         false);
740                 cloudMediaGenerator.createAlbum(cloudAlbum);
741             });
742 
743             // Verify that getLocalCategories only returns local albums
744             try (Cursor c = mItemsProvider.getLocalCategories(/* mimeType */ null,
745                     /* userId */ null)) {
746                 assertGetCategoriesMatchMultiple(c, Arrays.asList(
747                         Pair.create(ALBUM_ID_VIDEOS, 1),
748                         Pair.create(ALBUM_ID_SCREENSHOTS, 1)
749                 ));
750             }
751 
752 
753             // Verify that getAllCategories returns local + cloud albums
754             try (Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null,
755                     /* userId */ null)) {
756                 assertGetCategoriesMatchMultiple(c, Arrays.asList(
757                         Pair.create(ALBUM_ID_VIDEOS, 2),
758                         Pair.create(ALBUM_ID_SCREENSHOTS, 1),
759                         Pair.create(cloudAlbum, 1)
760                 ));
761             }
762         } finally {
763             videoFile.delete();
764             screenshotFile.delete();
765             setCloudProvider(null);
766             mConfigStore.clearAllowedCloudProviderPackagesAndDisableCloudMediaFeature();
767         }
768     }
769 
setupCloudProvider(Consumer<MediaGenerator> initMedia)770     private void setupCloudProvider(Consumer<MediaGenerator> initMedia) {
771         MediaGenerator cloudPrimaryMediaGenerator =
772                 PickerProviderMediaGenerator.getMediaGenerator(CloudProviderPrimary.AUTHORITY);
773         cloudPrimaryMediaGenerator.resetAll();
774         cloudPrimaryMediaGenerator.setMediaCollectionId("COLLECTION_1");
775         initMedia.accept(cloudPrimaryMediaGenerator);
776 
777         // Set the newly initialized cloud provider.
778         setCloudProvider(CloudProviderPrimary.AUTHORITY);
779 
780         mIsolatedResolver.call(MediaStore.AUTHORITY, MediaStore.SYNC_PROVIDERS_CALL, null,
781                 Bundle.EMPTY);
782     }
783 
setCloudProvider(String authority)784     private void setCloudProvider(String authority) {
785         Bundle in = new Bundle();
786         in.putString(MediaStore.EXTRA_CLOUD_PROVIDER, authority);
787         mIsolatedResolver.call(MediaStore.AUTHORITY, MediaStore.SET_CLOUD_PROVIDER_CALL, null, in);
788         if (authority != null) {
789             assertWithMessage("Expected " + authority + " to be current cloud provider.")
790                     .that(MediaStore.isCurrentCloudMediaProviderAuthority(mIsolatedResolver,
791                             authority)).isTrue();
792         }
793     }
794 
assertGetCategoriesMatchSingle(String expectedCategoryName, int expectedNumberOfItems)795     private void assertGetCategoriesMatchSingle(String expectedCategoryName,
796             int expectedNumberOfItems) throws Exception {
797         if (expectedNumberOfItems == 0) {
798             assertCategoriesNoMatch(expectedCategoryName);
799             return;
800         }
801 
802         Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null);
803         assertThat(c).isNotNull();
804         assertThat(c.getCount()).isEqualTo(1);
805 
806         // Assert that only expected category is returned and has expectedNumberOfItems items in it
807         assertThat(c.moveToFirst()).isTrue();
808         final int nameColumnIndex = c.getColumnIndexOrThrow(AlbumColumns.DISPLAY_NAME);
809         final int numOfItemsColumnIndex = c.getColumnIndexOrThrow(AlbumColumns.MEDIA_COUNT);
810         final int coverIdIndex = c.getColumnIndexOrThrow(AlbumColumns.MEDIA_COVER_ID);
811 
812         final String categoryName = c.getString(nameColumnIndex);
813         final int numOfItems = c.getInt(numOfItemsColumnIndex);
814         final Uri coverUri = ItemsProvider.getItemsUri(c.getString(coverIdIndex),
815                 LOCAL_PICKER_PROVIDER_AUTHORITY, UserId.CURRENT_USER);
816 
817         assertThat(categoryName).isEqualTo(expectedCategoryName);
818         assertThat(numOfItems).isEqualTo(expectedNumberOfItems);
819         assertCategoryUriIsValid(coverUri);
820     }
821 
assertCategoryUriIsValid(Uri uri)822     private void assertCategoryUriIsValid(Uri uri) throws Exception {
823         try (AssetFileDescriptor fd1 = mIsolatedResolver.openTypedAssetFile(uri, "image/*",
824                 null, null)) {
825             assertThat(fd1).isNotNull();
826         }
827         try (ParcelFileDescriptor fd2 = mIsolatedResolver.openFileDescriptor(uri, "r")) {
828             assertThat(fd2).isNotNull();
829         }
830     }
831 
assertCategoriesNoMatch(String expectedCategoryName)832     private void assertCategoriesNoMatch(String expectedCategoryName) {
833         try (Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null)) {
834             while (c != null && c.moveToNext()) {
835                 final int nameColumnIndex = c.getColumnIndexOrThrow(AlbumColumns.DISPLAY_NAME);
836                 final String categoryName = c.getString(nameColumnIndex);
837                 assertThat(categoryName).isNotEqualTo(expectedCategoryName);
838             }
839         }
840     }
841 
assertGetCategoriesMatchMultiple(List<Pair<String, Integer>> categories)842     private void assertGetCategoriesMatchMultiple(List<Pair<String, Integer>> categories) {
843         try (Cursor c = mItemsProvider.getAllCategories(/* mimeType */ null, /* userId */ null)) {
844             assertGetCategoriesMatchMultiple(c, categories);
845         }
846     }
847 
assertGetCategoriesMatchMultiple(Cursor c, List<Pair<String, Integer>> categories)848     private void assertGetCategoriesMatchMultiple(Cursor c,
849             List<Pair<String, Integer>> categories) {
850         assertThat(c).isNotNull();
851         assertWithMessage("Expected number of albums")
852                 .that(c.getCount()).isEqualTo(categories.size());
853 
854         final int nameColumnIndex = c.getColumnIndexOrThrow(AlbumColumns.DISPLAY_NAME);
855         final int numOfItemsColumnIndex = c.getColumnIndexOrThrow(
856                 AlbumColumns.MEDIA_COUNT);
857         for (Pair<String, Integer> category : categories) {
858             c.moveToNext();
859 
860             assertThat(category.first).isEqualTo(c.getString(nameColumnIndex));
861             assertWithMessage("Expected item count for " + category.first).that(
862                     c.getInt(numOfItemsColumnIndex)).isEqualTo(category.second);
863         }
864     }
865 
setIsFavorite(File file)866     private void setIsFavorite(File file) {
867         final Uri uri = MediaStore.scanFile(mIsolatedResolver, file);
868         final ContentValues values = new ContentValues();
869         values.put(MediaStore.MediaColumns.IS_FAVORITE, 1);
870         // Assert that 1 row corresponding to this file is updated.
871         assertThat(mIsolatedResolver.update(uri, values, null)).isEqualTo(1);
872         // Wait for MediaStore to be Idle to reduce flakes caused by database updates
873         MediaStore.waitForIdle(mIsolatedResolver);
874     }
875 
assertThatOnlyImagesVideos(Cursor c)876     private void assertThatOnlyImagesVideos(Cursor c) throws Exception {
877         while (c.moveToNext()) {
878             int mimeTypeColumn = c.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE);
879             String mimeType = c.getString(mimeTypeColumn);
880             assertThat(isImageMimeType(mimeType) || isVideoMimeType(mimeType)).isTrue();
881         }
882     }
883 
assertThatOnlyImages(Cursor c)884     private void assertThatOnlyImages(Cursor c) throws Exception {
885         while (c.moveToNext()) {
886             int mimeTypeColumn = c.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE);
887             String mimeType = c.getString(mimeTypeColumn);
888             assertThat(isImageMimeType(mimeType)).isTrue();
889         }
890     }
891 
assertThatOnlyVideos(Cursor c)892     private void assertThatOnlyVideos(Cursor c) throws Exception {
893         while (c.moveToNext()) {
894             int mimeTypeColumn = c.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE);
895             String mimeType = c.getString(mimeTypeColumn);
896             assertThat(isVideoMimeType(mimeType)).isTrue();
897         }
898     }
899 
assertThatAllImagesVideos(int count)900     private void assertThatAllImagesVideos(int count) {
901         int countOfImages = getCountOfMediaStoreImages();
902         int countOfVideos = getCountOfMediaStoreVideos();
903         assertThat(count).isEqualTo(countOfImages + countOfVideos);
904     }
905 
assertThatAllImages(int count)906     private void assertThatAllImages(int count) {
907         int countOfImages = getCountOfMediaStoreImages();
908         assertThat(count).isEqualTo(countOfImages);
909     }
910 
assertThatAllVideos(int count)911     private void assertThatAllVideos(int count) {
912         int countOfVideos = getCountOfMediaStoreVideos();
913         assertThat(count).isEqualTo(countOfVideos);
914     }
915 
createTestScreenshotImages()916     private void createTestScreenshotImages() throws IOException {
917         // Top Level /Screenshots/ directory is not allowed by MediaProvider, so the directory
918         // and test files in it are created via shell commands.
919 
920         final String createTopLevelScreenshotDirCommand =
921                 "mkdir -p "
922                         + Environment.getExternalStorageDirectory().getPath()
923                         + "/"
924                         + Environment.DIRECTORY_SCREENSHOTS;
925         final String createTopLevelScreenshotImgCommand =
926                 "touch " + getTopLevelScreenshotsDir().getPath() + "/" + IMAGE_FILE_NAME;
927         final String writeDataTopLevelScreenshotImgCommand =
928                 "echo 1 > " + getTopLevelScreenshotsDir().getPath() + "/" + IMAGE_FILE_NAME;
929 
930         executeShellCommand(createTopLevelScreenshotDirCommand);
931         executeShellCommand(createTopLevelScreenshotImgCommand);
932         // Writes 1 byte to the file.
933         executeShellCommand(writeDataTopLevelScreenshotImgCommand);
934 
935         final File topLevelScreenshotsDirImage =
936                 new File(getTopLevelScreenshotsDir(), IMAGE_FILE_NAME);
937 
938         // Force the mock MediaProvider to scan.
939         final Uri uri = MediaStore.scanFile(mIsolatedResolver, topLevelScreenshotsDirImage);
940         assertWithMessage("Uri obtained by scanning file " + topLevelScreenshotsDirImage)
941                 .that(uri)
942                 .isNotNull();
943         // Wait for picker db sync
944         MediaStore.waitForIdle(mIsolatedResolver);
945     }
946 
getCountOfMediaStoreImages()947     private int getCountOfMediaStoreImages() {
948         try (Cursor c = mIsolatedResolver.query(
949                 MediaStore.Images.Media.getContentUri(VOLUME_EXTERNAL), null, null, null)) {
950             assertThat(c.moveToFirst()).isTrue();
951             return c.getCount();
952         }
953     }
954 
getCountOfMediaStoreVideos()955     private int getCountOfMediaStoreVideos() {
956         try (Cursor c = mIsolatedResolver.query(
957                 MediaStore.Video.Media.getContentUri(VOLUME_EXTERNAL), null, null, null)) {
958             assertThat(c.moveToFirst()).isTrue();
959             return c.getCount();
960         }
961     }
962 
assertCreateNewVideo(File dir)963     private File assertCreateNewVideo(File dir) throws Exception {
964         return assertCreateNewFile(dir, VIDEO_FILE_NAME);
965     }
966 
assertCreateNewImage(File dir)967     private File assertCreateNewImage(File dir) throws Exception {
968         return assertCreateNewFile(dir, IMAGE_FILE_NAME);
969     }
970 
assertCreateNewVideo()971     private File assertCreateNewVideo() throws Exception {
972         return assertCreateNewFile(getDownloadsDir(), VIDEO_FILE_NAME);
973     }
974 
assertCreateNewImage()975     private File assertCreateNewImage() throws Exception {
976         return assertCreateNewFile(getDownloadsDir(), IMAGE_FILE_NAME);
977     }
978 
assertCreateNewFile(File parentDir, String fileName)979     private File assertCreateNewFile(File parentDir, String fileName) throws Exception {
980         final File file = new File(parentDir, fileName);
981         prepareFileAndGetUri(file, /* lastModifiedTime */ -1);
982 
983         return file;
984     }
985 
prepareFileAndGetUri(File file, long lastModifiedTime)986     private Uri prepareFileAndGetUri(File file, long lastModifiedTime) throws IOException {
987         ensureParentExists(file.getParentFile());
988 
989         assertThat(file.createNewFile()).isTrue();
990 
991         // Write 1 byte because 0byte files are not valid in the picker db
992         try (FileOutputStream fos = new FileOutputStream(file)) {
993             fos.write(1);
994         }
995 
996         if (lastModifiedTime != -1) {
997             file.setLastModified(lastModifiedTime);
998         }
999 
1000         final Uri uri = MediaStore.scanFile(mIsolatedResolver, file);
1001         assertWithMessage("Uri obtained by scanning file " + file)
1002                 .that(uri).isNotNull();
1003         // Wait for picker db sync
1004         MediaStore.waitForIdle(mIsolatedResolver);
1005 
1006         return uri;
1007     }
1008 
ensureParentExists(File parent)1009     private void ensureParentExists(File parent) {
1010         if (!parent.exists()) {
1011             parent.mkdirs();
1012         }
1013         assertThat(parent.exists()).isTrue();
1014     }
1015 
getDownloadsDir()1016     private File getDownloadsDir() {
1017         return new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS);
1018     }
1019 
getDcimDir()1020     private File getDcimDir() {
1021         return new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DCIM);
1022     }
1023 
getPicturesDir()1024     private File getPicturesDir() {
1025         return new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_PICTURES);
1026     }
1027 
getMoviesDir()1028     private File getMoviesDir() {
1029         return new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_MOVIES);
1030     }
1031 
getCameraDir()1032     private File getCameraDir() {
1033         return new File(getDcimDir(), "Camera");
1034     }
1035 
getScreenshotsDir()1036     private File getScreenshotsDir() {
1037         return new File(getPicturesDir(), Environment.DIRECTORY_SCREENSHOTS);
1038     }
1039 
getTopLevelScreenshotsDir()1040     private File getTopLevelScreenshotsDir() {
1041         return new File(
1042                 Environment.getExternalStorageDirectory(), Environment.DIRECTORY_SCREENSHOTS);
1043     }
1044 
getScreenshotsDirFromDownloadsDir()1045     private File getScreenshotsDirFromDownloadsDir() {
1046         return new File(getDownloadsDir(), Environment.DIRECTORY_SCREENSHOTS);
1047     }
1048 
createHiddenDir()1049     private File createHiddenDir() throws Exception {
1050         File parentDir = new File(Environment.getExternalStorageDirectory(),
1051                 Environment.DIRECTORY_DOWNLOADS);
1052         File dir = new File(parentDir, HIDDEN_DIR_NAME);
1053         dir.mkdirs();
1054         File nomedia = new File(dir, ".nomedia");
1055         nomedia.createNewFile();
1056 
1057         MediaStore.scanFile(mIsolatedResolver, nomedia);
1058 
1059         return dir;
1060     }
1061 
deleteAllFilesNoThrow()1062     private void deleteAllFilesNoThrow() {
1063         try (Cursor c = mIsolatedResolver.query(
1064                 MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
1065                 new String[] {MediaStore.MediaColumns.DATA}, null, null)) {
1066             while(c.moveToNext()) {
1067                 (new File(c.getString(
1068                         c.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)))).delete();
1069             }
1070         }
1071     }
1072 
deleteTopLevelScreenshotDir()1073     private void deleteTopLevelScreenshotDir() throws IOException {
1074         final String removeTopLevelScreenshotDirCommand =
1075                 "rm -rf " + getTopLevelScreenshotsDir().getPath();
1076         executeShellCommand(removeTopLevelScreenshotDirCommand);
1077     }
1078 
1079     /** Executes a shell command. */
executeShellCommand(String command)1080     private static String executeShellCommand(String command) throws IOException {
1081         int attempt = 0;
1082         while (attempt++ < 5) {
1083             try {
1084                 return executeShellCommandInternal(command);
1085             } catch (InterruptedIOException e) {
1086                 Log.v(TAG, "Trouble executing " + command + "; trying again", e);
1087             }
1088         }
1089         throw new IOException("Failed to execute " + command);
1090     }
1091 
executeShellCommandInternal(String cmd)1092     private static String executeShellCommandInternal(String cmd) throws IOException {
1093         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
1094         try (FileInputStream output = new FileInputStream(
1095                 uiAutomation.executeShellCommand(cmd).getFileDescriptor())) {
1096             return new String(ByteStreams.toByteArray(output));
1097         }
1098     }
1099 }
1100