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.util.MimeUtils.isImageMimeType; 29 import static com.android.providers.media.util.MimeUtils.isVideoMimeType; 30 31 import static com.google.common.truth.Truth.assertThat; 32 import static com.google.common.truth.Truth.assertWithMessage; 33 34 import android.Manifest; 35 import android.app.UiAutomation; 36 import android.content.ContentResolver; 37 import android.content.ContentValues; 38 import android.content.Context; 39 import android.content.res.AssetFileDescriptor; 40 import android.database.Cursor; 41 import android.net.Uri; 42 import android.os.Environment; 43 import android.os.ParcelFileDescriptor; 44 import android.provider.MediaStore; 45 46 import androidx.test.InstrumentationRegistry; 47 48 import com.android.providers.media.photopicker.data.ItemsProvider; 49 import com.android.providers.media.photopicker.data.model.Category; 50 import com.android.providers.media.photopicker.data.model.UserId; 51 import com.android.providers.media.scan.MediaScannerTest.IsolatedContext; 52 53 import org.junit.Before; 54 import org.junit.Test; 55 56 import java.io.File; 57 import java.io.FileOutputStream; 58 import java.io.IOException; 59 import java.util.ArrayList; 60 import java.util.List; 61 62 public class ItemsProviderTest { 63 64 /** 65 * To help avoid flaky tests, give ourselves a unique nonce to be used for 66 * all filesystem paths, so that we don't risk conflicting with previous 67 * test runs. 68 */ 69 private static final String NONCE = String.valueOf(System.nanoTime()); 70 private static final String TAG = "ItemsProviderTest"; 71 private static final String VIDEO_FILE_NAME = TAG + "_file_" + NONCE + ".mp4"; 72 private static final String IMAGE_FILE_NAME = TAG + "_file_" + NONCE + ".jpg"; 73 private static final String HIDDEN_DIR_NAME = TAG + "_hidden_dir_" + NONCE; 74 75 private ContentResolver mIsolatedResolver; 76 private ItemsProvider mItemsProvider; 77 78 @Before setUp()79 public void setUp() { 80 final UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation() 81 .getUiAutomation(); 82 83 uiAutomation.adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE, 84 Manifest.permission.READ_COMPAT_CHANGE_CONFIG, 85 Manifest.permission.READ_DEVICE_CONFIG, 86 Manifest.permission.INTERACT_ACROSS_USERS); 87 88 // Remove sync delay to avoid flaky tests 89 final String setSyncDelayCommand = 90 "device_config put storage pickerdb.default_sync_delay_ms 0"; 91 uiAutomation.executeShellCommand(setSyncDelayCommand); 92 93 final Context context = InstrumentationRegistry.getTargetContext(); 94 final Context isolatedContext 95 = new IsolatedContext(context, "databases", /*asFuseThread*/ false); 96 mIsolatedResolver = isolatedContext.getContentResolver(); 97 mItemsProvider = new ItemsProvider(isolatedContext); 98 99 // Wait for MediaStore to be Idle to reduce flakes caused by database updates 100 MediaStore.waitForIdle(mIsolatedResolver); 101 } 102 103 /** 104 * Tests {@link ItemsProvider#getCategories(String, UserId)} to return correct info about 105 * {@link #ALBUM_ID_CAMERA}. 106 */ 107 @Test testGetCategories_camera()108 public void testGetCategories_camera() throws Exception { 109 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 110 assertThat(c.getCount()).isEqualTo(0); 111 112 // Create 1 image file in Camera dir to test 113 // {@link ItemsProvider#getCategories(String, UserId)}. 114 final File cameraDir = getCameraDir(); 115 File imageFile = assertCreateNewImage(cameraDir); 116 try { 117 assertGetCategoriesMatchSingle(ALBUM_ID_CAMERA, /* numberOfItems */ 1); 118 } finally { 119 imageFile.delete(); 120 } 121 } 122 123 /** 124 * Tests {@link ItemsProvider#getCategories(String, UserId)} to return correct info about 125 * {@link #ALBUM_ID_CAMERA}. 126 */ 127 @Test testGetCategories_not_camera()128 public void testGetCategories_not_camera() throws Exception { 129 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 130 assertThat(c.getCount()).isEqualTo(0); 131 132 // negative test case: image file which should not be returned in Camera category 133 final File picturesDir = getPicturesDir(); 134 File nonCameraImageFile = assertCreateNewImage(picturesDir); 135 try { 136 assertGetCategoriesMatchSingle(ALBUM_ID_CAMERA, /* numberOfItems */ 0); 137 } finally { 138 nonCameraImageFile.delete(); 139 } 140 } 141 142 /** 143 * Tests {@link ItemsProvider#getCategories(String, UserId)} to return correct info about 144 * {@link #ALBUM_ID_VIDEOS}. 145 */ 146 @Test testGetCategories_videos()147 public void testGetCategories_videos() throws Exception { 148 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 149 assertThat(c.getCount()).isEqualTo(0); 150 151 // Create 1 video file in Movies dir to test 152 // {@link ItemsProvider#getCategories(String, UserId)}. 153 final File moviesDir = getMoviesDir(); 154 File videoFile = assertCreateNewVideo(moviesDir); 155 try { 156 assertGetCategoriesMatchSingle(ALBUM_ID_VIDEOS, /* numberOfItems */ 1); 157 } finally { 158 videoFile.delete(); 159 } 160 } 161 162 /** 163 * Tests {@link ItemsProvider#getCategories(String, UserId)} to return correct info about 164 * {@link #ALBUM_ID_VIDEOS}. 165 */ 166 @Test testGetCategories_not_videos()167 public void testGetCategories_not_videos() throws Exception { 168 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 169 assertThat(c.getCount()).isEqualTo(0); 170 171 // negative test case: image file which should not be returned in Videos category 172 final File picturesDir = getPicturesDir(); 173 File imageFile = assertCreateNewImage(picturesDir); 174 try { 175 assertGetCategoriesMatchSingle(ALBUM_ID_VIDEOS, /* numberOfItems */ 0); 176 } finally { 177 imageFile.delete(); 178 } 179 } 180 181 /** 182 * Tests {@link ItemsProvider#getCategories(String, UserId)} to return correct info about 183 * {@link #ALBUM_ID_SCREENSHOTS}. 184 */ 185 @Test testGetCategories_screenshots()186 public void testGetCategories_screenshots() throws Exception { 187 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 188 assertThat(c.getCount()).isEqualTo(0); 189 190 // Create 1 image file in Screenshots dir to test 191 // {@link ItemsProvider#getCategories(String, UserId)} 192 final File screenshotsDir = getScreenshotsDir(); 193 File imageFile = assertCreateNewImage(screenshotsDir); 194 // Create 1 image file in Screenshots dir of Downloads dir 195 // {@link ItemsProvider#getCategories(String, UserId)} 196 final File screenshotsDirInDownloadsDir = getScreenshotsDirFromDownloadsDir(); 197 File imageFileInScreenshotDirInDownloads = 198 assertCreateNewImage(screenshotsDirInDownloadsDir); 199 try { 200 assertGetCategoriesMatchMultiple(ALBUM_ID_SCREENSHOTS, 201 ALBUM_ID_DOWNLOADS, /* numberOfItemsInScreenshots */ 2, 202 /* numberOfItemsInDownloads */ 1); 203 } finally { 204 imageFile.delete(); 205 imageFileInScreenshotDirInDownloads.delete(); 206 } 207 } 208 209 /** 210 * Tests {@link ItemsProvider#getCategories(String, UserId)} to return correct info about 211 * {@link #ALBUM_ID_SCREENSHOTS}. 212 */ 213 @Test testGetCategories_not_screenshots()214 public void testGetCategories_not_screenshots() throws Exception { 215 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 216 assertThat(c.getCount()).isEqualTo(0); 217 218 // negative test case: image file which should not be returned in Screenshots category 219 final File cameraDir = getCameraDir(); 220 File imageFile = assertCreateNewImage(cameraDir); 221 try { 222 assertGetCategoriesMatchSingle(ALBUM_ID_SCREENSHOTS, /* numberOfItems */ 0); 223 } finally { 224 imageFile.delete(); 225 } 226 } 227 228 /** 229 * Tests {@link ItemsProvider#getCategories(String, UserId)} to return correct info about 230 * {@link AlbumColumns#ALBUM_ID_FAVORITES}. 231 */ 232 @Test testGetCategories_favorites()233 public void testGetCategories_favorites() throws Exception { 234 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 235 assertThat(c.getCount()).isEqualTo(0); 236 237 // positive test case: image file which should be returned in favorites category 238 final File picturesDir = getPicturesDir(); 239 final File imageFile = assertCreateNewImage(picturesDir); 240 setIsFavorite(imageFile); 241 try { 242 assertGetCategoriesMatchSingle(ALBUM_ID_FAVORITES, /* numberOfItems */1); 243 } finally { 244 imageFile.delete(); 245 } 246 } 247 248 /** 249 * Tests {@link ItemsProvider#getCategories(String, UserId)} to return correct info about 250 * {@link AlbumColumns#ALBUM_ID_FAVORITES}. 251 */ 252 @Test testGetCategories_not_favorites()253 public void testGetCategories_not_favorites() throws Exception { 254 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 255 assertThat(c.getCount()).isEqualTo(0); 256 257 // negative test case: image file which should not be returned in favorites category 258 final File picturesDir = getPicturesDir(); 259 final File nonFavImageFile = assertCreateNewImage(picturesDir); 260 try { 261 assertGetCategoriesMatchSingle(ALBUM_ID_FAVORITES, /* numberOfItems */ 0); 262 } finally { 263 nonFavImageFile.delete(); 264 } 265 } 266 267 /** 268 * Tests {@link ItemsProvider#getCategories(String, UserId)} to return correct info about 269 * {@link #ALBUM_ID_DOWNLOADS}. 270 */ 271 @Test testGetCategories_downloads()272 public void testGetCategories_downloads() throws Exception { 273 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 274 assertThat(c.getCount()).isEqualTo(0); 275 276 // Create 1 image file in Downloads dir to test 277 // {@link ItemsProvider#getCategories(String, UserId)}. 278 final File downloadsDir = getDownloadsDir(); 279 final File imageFile = assertCreateNewImage(downloadsDir); 280 try { 281 assertGetCategoriesMatchSingle(ALBUM_ID_DOWNLOADS, /* numberOfItems */ 1); 282 } finally { 283 imageFile.delete(); 284 } 285 } 286 287 /** 288 * Tests {@link ItemsProvider#getCategories(String, UserId)} to return correct info about 289 * {@link #ALBUM_ID_DOWNLOADS}. 290 */ 291 @Test testGetCategories_not_downloads()292 public void testGetCategories_not_downloads() throws Exception { 293 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 294 assertThat(c.getCount()).isEqualTo(0); 295 296 // negative test case: image file which should not be returned in Downloads category 297 final File picturesDir = getPicturesDir(); 298 final File nonDownloadsImageFile = assertCreateNewImage(picturesDir); 299 try { 300 assertGetCategoriesMatchSingle(ALBUM_ID_DOWNLOADS, /* numberOfItems */ 0); 301 } finally { 302 nonDownloadsImageFile.delete(); 303 } 304 } 305 306 /** 307 * Tests {@link ItemsProvider#getCategories(String, UserId)} to return correct info about 308 * {@link #ALBUM_ID_CAMERA} and {@link #ALBUM_ID_VIDEOS}. 309 */ 310 @Test testGetCategories_camera_and_videos()311 public void testGetCategories_camera_and_videos() throws Exception { 312 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 313 assertThat(c.getCount()).isEqualTo(0); 314 315 // Create 1 video file in Camera dir to test 316 // {@link ItemsProvider#getCategories(String, UserId)}. 317 final File cameraDir = getCameraDir(); 318 File videoFile = assertCreateNewVideo(cameraDir); 319 try { 320 assertGetCategoriesMatchMultiple(ALBUM_ID_CAMERA, ALBUM_ID_VIDEOS, 321 /* numberOfItemsInCamera */ 1, 322 /* numberOfItemsInVideos */ 1); 323 } finally { 324 videoFile.delete(); 325 } 326 } 327 328 /** 329 * Tests {@link ItemsProvider#getCategories(String, UserId)} to return correct info about 330 * {@link AlbumColumns#ALBUM_ID_SCREENSHOTS} and {@link AlbumColumns#ALBUM_ID_FAVORITES}. 331 */ 332 @Test testGetCategories_screenshots_and_favorites()333 public void testGetCategories_screenshots_and_favorites() throws Exception { 334 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 335 assertThat(c.getCount()).isEqualTo(0); 336 337 // Create 1 image file in Screenshots dir to test 338 // {@link ItemsProvider#getCategories(String, UserId)} 339 final File screenshotsDir = getScreenshotsDir(); 340 File imageFile = assertCreateNewImage(screenshotsDir); 341 setIsFavorite(imageFile); 342 try { 343 assertGetCategoriesMatchMultiple(ALBUM_ID_SCREENSHOTS, 344 ALBUM_ID_FAVORITES, 345 /* numberOfItemsInScreenshots */ 1, 346 /* numberOfItemsInFavorites */ 1); 347 } finally { 348 imageFile.delete(); 349 } 350 } 351 352 /** 353 * Tests {@link ItemsProvider#getCategories(String, UserId)} to return correct info about 354 * {@link AlbumColumns#ALBUM_ID_DOWNLOADS} and {@link AlbumColumns#ALBUM_ID_FAVORITES}. 355 */ 356 @Test testGetCategories_downloads_and_favorites()357 public void testGetCategories_downloads_and_favorites() throws Exception { 358 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 359 assertThat(c.getCount()).isEqualTo(0); 360 361 // Create 1 image file in Screenshots dir to test 362 // {@link ItemsProvider#getCategories(String, UserId)} 363 final File downloadsDir = getDownloadsDir(); 364 File imageFile = assertCreateNewImage(downloadsDir); 365 setIsFavorite(imageFile); 366 try { 367 assertGetCategoriesMatchMultiple(ALBUM_ID_DOWNLOADS, 368 ALBUM_ID_FAVORITES, 369 /* numberOfItemsInScreenshots */ 1, 370 /* numberOfItemsInFavorites */ 1); 371 } finally { 372 imageFile.delete(); 373 } 374 } 375 376 /** 377 * Tests {@link ItemsProvider#getItems(String, int, int, String, UserId)} to return all 378 * images and videos. 379 */ 380 @Test testGetItems()381 public void testGetItems() throws Exception { 382 // Create 1 image and 1 video file to test 383 // {@link ItemsProvider#getItems(String, int, int, String, UserId)}. 384 // Both files should be returned. 385 File imageFile = assertCreateNewImage(); 386 File videoFile = assertCreateNewVideo(); 387 try { 388 final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, 389 /* limit */ -1, /* mimeType */ null, /* userId */ null); 390 assertThat(res).isNotNull(); 391 assertThat(res.getCount()).isEqualTo(2); 392 393 assertThatOnlyImagesVideos(res); 394 // Reset the cursor back. Cursor#moveToPosition(-1) will reset the position to -1, 395 // but since there is no such valid cursor position, it returns false. 396 assertThat(res.moveToPosition(-1)).isFalse(); 397 assertThatAllImagesVideos(res.getCount()); 398 } finally { 399 imageFile.delete(); 400 videoFile.delete(); 401 } 402 } 403 404 @Test testGetItems_sortOrder()405 public void testGetItems_sortOrder() throws Exception { 406 try { 407 final long timeNow = System.nanoTime() / 1000; 408 final Uri imageFileDateNowPlus1Uri = prepareFileAndGetUri( 409 new File(getDownloadsDir(), "latest_" + IMAGE_FILE_NAME), timeNow + 1000); 410 final Uri imageFileDateNowUri 411 = prepareFileAndGetUri(new File(getDcimDir(), IMAGE_FILE_NAME), timeNow); 412 final Uri videoFileDateNowUri 413 = prepareFileAndGetUri(new File(getCameraDir(), VIDEO_FILE_NAME), timeNow); 414 415 // This is the list of uris based on the expected sort order of items returned by 416 // ItemsProvider#getItems 417 List<Uri> uris = new ArrayList<>(); 418 // This is the latest image file 419 uris.add(imageFileDateNowPlus1Uri); 420 // Video file was scanned after image file, hence has higher _id than image file 421 uris.add(videoFileDateNowUri); 422 uris.add(imageFileDateNowUri); 423 424 try (Cursor cursor = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, 425 /* limit */ -1, /* mimeType */ null, /* userId */ null)) { 426 assertThat(cursor).isNotNull(); 427 428 final int expectedCount = uris.size(); 429 assertThat(cursor.getCount()).isEqualTo(expectedCount); 430 431 int rowNum = 0; 432 assertThat(cursor.moveToFirst()).isTrue(); 433 final int idColumnIndex = cursor.getColumnIndexOrThrow(MediaColumns.ID); 434 while (rowNum < expectedCount) { 435 assertWithMessage("id at row:" + rowNum + " is expected to be" 436 + " same as id in " + uris.get(rowNum)) 437 .that(String.valueOf(cursor.getLong(idColumnIndex))) 438 .isEqualTo(uris.get(rowNum).getLastPathSegment()); 439 cursor.moveToNext(); 440 rowNum++; 441 } 442 } 443 } finally { 444 deleteAllFilesNoThrow(); 445 } 446 } 447 448 /** 449 * Tests {@link {@link ItemsProvider#getItems(String, int, int, String, UserId)}} does not 450 * return hidden images/videos. 451 */ 452 @Test testGetItems_nonMedia()453 public void testGetItems_nonMedia() throws Exception { 454 // Create 1 image and 1 video file in a hidden dir to test 455 // {@link ItemsProvider#getItems(String, int, int, String, UserId)}. 456 // Both should not be returned. 457 File hiddenDir = createHiddenDir(); 458 File imageFileHidden = assertCreateNewImage(hiddenDir); 459 File videoFileHidden = assertCreateNewVideo(hiddenDir); 460 try { 461 final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, 462 /* limit */ -1, /* mimeType */ null, /* userId */ null); 463 assertThat(res).isNotNull(); 464 assertThat(res.getCount()).isEqualTo(0); 465 } finally { 466 imageFileHidden.delete(); 467 videoFileHidden.delete(); 468 hiddenDir.delete(); 469 } 470 } 471 472 /** 473 * Tests {@link ItemsProvider#getItems(String, int, int, String, UserId)} to return all 474 * images and videos based on the mimeType. Image mimeType should only return images. 475 */ 476 @Test testGetItemsImages()477 public void testGetItemsImages() throws Exception { 478 // Create 1 image and 1 video file to test 479 // {@link ItemsProvider#getItems(String, int, int, String, UserId)}. 480 // Only 1 should be returned. 481 File imageFile = assertCreateNewImage(); 482 File videoFile = assertCreateNewVideo(); 483 try { 484 final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, 485 /* limit */ -1, /* mimeType */ "image/*", /* userId */ null); 486 assertThat(res).isNotNull(); 487 assertThat(res.getCount()).isEqualTo(1); 488 489 assertThatOnlyImages(res); 490 assertThatAllImages(res.getCount()); 491 } finally { 492 imageFile.delete(); 493 videoFile.delete(); 494 } 495 } 496 497 /** 498 * Tests {@link ItemsProvider#getItems(String, int, int, String, UserId)} to return all 499 * images and videos based on the mimeType. Image mimeType should only return images. 500 */ 501 @Test testGetItemsImages_png()502 public void testGetItemsImages_png() throws Exception { 503 // Create a jpg file image. Tests negative use case, this should not be returned below. 504 File imageFile = assertCreateNewImage(); 505 try { 506 final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, 507 /* limit */ -1, /* mimeType */ "image/png", /* userId */ null); 508 assertThat(res).isNotNull(); 509 assertThat(res.getCount()).isEqualTo(0); 510 } finally { 511 imageFile.delete(); 512 } 513 } 514 515 /** 516 * Tests {@link ItemsProvider#getItems(String, int, int, String, UserId)} does not return 517 * hidden images/videos. 518 */ 519 @Test testGetItemsImages_nonMedia()520 public void testGetItemsImages_nonMedia() throws Exception { 521 // Create 1 image and 1 video file in a hidden dir to test 522 // {@link ItemsProvider#getItems(String, int, int, String)}. 523 // Both should not be returned. 524 File hiddenDir = createHiddenDir(); 525 File imageFileHidden = assertCreateNewImage(hiddenDir); 526 File videoFileHidden = assertCreateNewVideo(hiddenDir); 527 try { 528 final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, 529 /* limit */ -1, /* mimeType */ "image/*", /* userId */ null); 530 assertThat(res).isNotNull(); 531 assertThat(res.getCount()).isEqualTo(0); 532 } finally { 533 imageFileHidden.delete(); 534 videoFileHidden.delete(); 535 hiddenDir.delete(); 536 } 537 } 538 539 /** 540 * Tests {@link ItemsProvider#getItems(String, int, int, String, UserId)} to return all 541 * images and videos based on the mimeType. Video mimeType should only return videos. 542 */ 543 @Test testGetItemsVideos()544 public void testGetItemsVideos() throws Exception { 545 // Create 1 image and 1 video file to test 546 // {@link ItemsProvider#getItems(String, int, int, String)}. 547 // Only 1 should be returned. 548 File imageFile = assertCreateNewImage(); 549 File videoFile = assertCreateNewVideo(); 550 try { 551 final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, 552 /* limit */ -1, /* mimeType */ "video/*", /* userId */ null); 553 assertThat(res).isNotNull(); 554 assertThat(res.getCount()).isEqualTo(1); 555 556 assertThatOnlyVideos(res); 557 assertThatAllVideos(res.getCount()); 558 } finally { 559 imageFile.delete(); 560 videoFile.delete(); 561 } 562 } 563 564 /** 565 * Tests {@link ItemsProvider#getItems(String, int, int, String, UserId)} to return all 566 * images and videos based on the mimeType. Image mimeType should only return images. 567 */ 568 @Test testGetItemsVideos_mp4()569 public void testGetItemsVideos_mp4() throws Exception { 570 // Create a mp4 video file. Tests positive use case, this should be returned below. 571 File videoFile = assertCreateNewVideo(); 572 try { 573 final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, 574 /* limit */ -1, /* mimeType */ "video/mp4", /* userId */ null); 575 assertThat(res).isNotNull(); 576 assertThat(res.getCount()).isEqualTo(1); 577 } finally { 578 videoFile.delete(); 579 } 580 } 581 582 /** 583 * Tests {@link ItemsProvider#getItems(String, int, int, String, UserId)} does not return 584 * hidden images/videos. 585 */ 586 @Test testGetItemsVideos_nonMedia()587 public void testGetItemsVideos_nonMedia() throws Exception { 588 // Create 1 image and 1 video file in a hidden dir to test the API. 589 // Both should not be returned. 590 File hiddenDir = createHiddenDir(); 591 File imageFileHidden = assertCreateNewImage(hiddenDir); 592 File videoFileHidden = assertCreateNewVideo(hiddenDir); 593 try { 594 final Cursor res = mItemsProvider.getItems(Category.DEFAULT, /* offset */ 0, 595 /* limit */ -1, /* mimeType */ "video/*", /* userId */ null); 596 assertThat(res).isNotNull(); 597 assertThat(res.getCount()).isEqualTo(0); 598 } finally { 599 imageFileHidden.delete(); 600 videoFileHidden.delete(); 601 hiddenDir.delete(); 602 } 603 } 604 assertGetCategoriesMatchSingle(String expectedCategoryName, int expectedNumberOfItems)605 private void assertGetCategoriesMatchSingle(String expectedCategoryName, 606 int expectedNumberOfItems) throws Exception { 607 if (expectedNumberOfItems == 0) { 608 assertCategoriesNoMatch(expectedCategoryName); 609 return; 610 } 611 612 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 613 assertThat(c).isNotNull(); 614 assertThat(c.getCount()).isEqualTo(1); 615 616 // Assert that only expected category is returned and has expectedNumberOfItems items in it 617 assertThat(c.moveToFirst()).isTrue(); 618 final int nameColumnIndex = c.getColumnIndexOrThrow(AlbumColumns.DISPLAY_NAME); 619 final int numOfItemsColumnIndex = c.getColumnIndexOrThrow(AlbumColumns.MEDIA_COUNT); 620 final int coverIdIndex = c.getColumnIndexOrThrow(AlbumColumns.MEDIA_COVER_ID); 621 622 final String categoryName = c.getString(nameColumnIndex); 623 final int numOfItems = c.getInt(numOfItemsColumnIndex); 624 final Uri coverUri = ItemsProvider.getItemsUri(c.getString(coverIdIndex), 625 PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY, UserId.CURRENT_USER); 626 627 assertThat(categoryName).isEqualTo(expectedCategoryName); 628 assertThat(numOfItems).isEqualTo(expectedNumberOfItems); 629 assertCategoryUriIsValid(coverUri); 630 } 631 assertCategoryUriIsValid(Uri uri)632 private void assertCategoryUriIsValid(Uri uri) throws Exception { 633 try (AssetFileDescriptor fd1 = mIsolatedResolver.openTypedAssetFile(uri, "image/*", 634 null, null)) { 635 assertThat(fd1).isNotNull(); 636 } 637 try (ParcelFileDescriptor fd2 = mIsolatedResolver.openFileDescriptor(uri, "r")) { 638 assertThat(fd2).isNotNull(); 639 } 640 } 641 assertCategoriesNoMatch(String expectedCategoryName)642 private void assertCategoriesNoMatch(String expectedCategoryName) { 643 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 644 while (c != null && c.moveToNext()) { 645 final int nameColumnIndex = c.getColumnIndexOrThrow(AlbumColumns.DISPLAY_NAME); 646 final String categoryName = c.getString(nameColumnIndex); 647 assertThat(categoryName).isNotEqualTo(expectedCategoryName); 648 } 649 } 650 assertGetCategoriesMatchMultiple(String category1, String category2, int numberOfItems1, int numberOfItems2)651 private void assertGetCategoriesMatchMultiple(String category1, String category2, 652 int numberOfItems1, int numberOfItems2) { 653 Cursor c = mItemsProvider.getCategories(/* mimeType */ null, /* userId */ null); 654 assertThat(c).isNotNull(); 655 assertThat(c.getCount()).isEqualTo(2); 656 657 // Assert that category1 and category2 is returned and has numberOfItems1 and 658 // numberOfItems2 items in them respectively. 659 boolean isCategory1Returned = false; 660 boolean isCategory2Returned = false; 661 while (c.moveToNext()) { 662 final int nameColumnIndex = c.getColumnIndexOrThrow(AlbumColumns.DISPLAY_NAME); 663 final int numOfItemsColumnIndex = c.getColumnIndexOrThrow( 664 AlbumColumns.MEDIA_COUNT); 665 666 final String categoryName = c.getString(nameColumnIndex); 667 final int numOfItems = c.getInt(numOfItemsColumnIndex); 668 669 670 if (categoryName.equals(category1)) { 671 isCategory1Returned = true; 672 assertThat(numOfItems).isEqualTo(numberOfItems1); 673 } else if (categoryName.equals(category2)) { 674 isCategory2Returned = true; 675 assertThat(numOfItems).isEqualTo(numberOfItems2); 676 } 677 } 678 679 assertThat(isCategory1Returned).isTrue(); 680 assertThat(isCategory2Returned).isTrue(); 681 } 682 setIsFavorite(File file)683 private void setIsFavorite(File file) { 684 final Uri uri = MediaStore.scanFile(mIsolatedResolver, file); 685 final ContentValues values = new ContentValues(); 686 values.put(MediaStore.MediaColumns.IS_FAVORITE, 1); 687 // Assert that 1 row corresponding to this file is updated. 688 assertThat(mIsolatedResolver.update(uri, values, null)).isEqualTo(1); 689 // Wait for MediaStore to be Idle to reduce flakes caused by database updates 690 MediaStore.waitForIdle(mIsolatedResolver); 691 } 692 assertThatOnlyImagesVideos(Cursor c)693 private void assertThatOnlyImagesVideos(Cursor c) throws Exception { 694 while (c.moveToNext()) { 695 int mimeTypeColumn = c.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE); 696 String mimeType = c.getString(mimeTypeColumn); 697 assertThat(isImageMimeType(mimeType) || isVideoMimeType(mimeType)).isTrue(); 698 } 699 } 700 assertThatOnlyImages(Cursor c)701 private void assertThatOnlyImages(Cursor c) throws Exception { 702 while (c.moveToNext()) { 703 int mimeTypeColumn = c.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE); 704 String mimeType = c.getString(mimeTypeColumn); 705 assertThat(isImageMimeType(mimeType)).isTrue(); 706 } 707 } 708 assertThatOnlyVideos(Cursor c)709 private void assertThatOnlyVideos(Cursor c) throws Exception { 710 while (c.moveToNext()) { 711 int mimeTypeColumn = c.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE); 712 String mimeType = c.getString(mimeTypeColumn); 713 assertThat(isVideoMimeType(mimeType)).isTrue(); 714 } 715 } 716 assertThatAllImagesVideos(int count)717 private void assertThatAllImagesVideos(int count) { 718 int countOfImages = getCountOfMediaStoreImages(); 719 int countOfVideos = getCountOfMediaStoreVideos(); 720 assertThat(count).isEqualTo(countOfImages + countOfVideos); 721 } 722 assertThatAllImages(int count)723 private void assertThatAllImages(int count) { 724 int countOfImages = getCountOfMediaStoreImages(); 725 assertThat(count).isEqualTo(countOfImages); 726 } 727 assertThatAllVideos(int count)728 private void assertThatAllVideos(int count) { 729 int countOfVideos = getCountOfMediaStoreVideos(); 730 assertThat(count).isEqualTo(countOfVideos); 731 } 732 getCountOfMediaStoreImages()733 private int getCountOfMediaStoreImages() { 734 try (Cursor c = mIsolatedResolver.query( 735 MediaStore.Images.Media.getContentUri(VOLUME_EXTERNAL), null, null, null)) { 736 assertThat(c.moveToFirst()).isTrue(); 737 return c.getCount(); 738 } 739 } 740 getCountOfMediaStoreVideos()741 private int getCountOfMediaStoreVideos() { 742 try (Cursor c = mIsolatedResolver.query( 743 MediaStore.Video.Media.getContentUri(VOLUME_EXTERNAL), null, null, null)) { 744 assertThat(c.moveToFirst()).isTrue(); 745 return c.getCount(); 746 } 747 } 748 assertCreateNewVideo(File dir)749 private File assertCreateNewVideo(File dir) throws Exception { 750 return assertCreateNewFile(dir, VIDEO_FILE_NAME); 751 } 752 assertCreateNewImage(File dir)753 private File assertCreateNewImage(File dir) throws Exception { 754 return assertCreateNewFile(dir, IMAGE_FILE_NAME); 755 } 756 assertCreateNewVideo()757 private File assertCreateNewVideo() throws Exception { 758 return assertCreateNewFile(getDownloadsDir(), VIDEO_FILE_NAME); 759 } 760 assertCreateNewImage()761 private File assertCreateNewImage() throws Exception { 762 return assertCreateNewFile(getDownloadsDir(), IMAGE_FILE_NAME); 763 } 764 assertCreateNewFile(File parentDir, String fileName)765 private File assertCreateNewFile(File parentDir, String fileName) throws Exception { 766 final File file = new File(parentDir, fileName); 767 prepareFileAndGetUri(file, /* lastModifiedTime */ -1); 768 769 return file; 770 } 771 prepareFileAndGetUri(File file, long lastModifiedTime)772 private Uri prepareFileAndGetUri(File file, long lastModifiedTime) throws IOException { 773 ensureParentExists(file.getParentFile()); 774 775 assertThat(file.createNewFile()).isTrue(); 776 777 // Write 1 byte because 0byte files are not valid in the picker db 778 try (FileOutputStream fos = new FileOutputStream(file)) { 779 fos.write(1); 780 } 781 782 if (lastModifiedTime != -1) { 783 file.setLastModified(lastModifiedTime); 784 } 785 786 final Uri uri = MediaStore.scanFile(mIsolatedResolver, file); 787 assertWithMessage("Uri obtained by scanning file " + file) 788 .that(uri).isNotNull(); 789 // Wait for picker db sync 790 MediaStore.waitForIdle(mIsolatedResolver); 791 792 return uri; 793 } 794 ensureParentExists(File parent)795 private void ensureParentExists(File parent) { 796 if (!parent.exists()) { 797 parent.mkdirs(); 798 } 799 assertThat(parent.exists()).isTrue(); 800 } 801 getDownloadsDir()802 private File getDownloadsDir() { 803 return new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); 804 } 805 getDcimDir()806 private File getDcimDir() { 807 return new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DCIM); 808 } 809 getPicturesDir()810 private File getPicturesDir() { 811 return new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_PICTURES); 812 } 813 getMoviesDir()814 private File getMoviesDir() { 815 return new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_MOVIES); 816 } 817 getCameraDir()818 private File getCameraDir() { 819 return new File(getDcimDir(), "Camera"); 820 } 821 getScreenshotsDir()822 private File getScreenshotsDir() { 823 return new File(getPicturesDir(), Environment.DIRECTORY_SCREENSHOTS); 824 } 825 getScreenshotsDirFromDownloadsDir()826 private File getScreenshotsDirFromDownloadsDir() { 827 return new File(getDownloadsDir(), Environment.DIRECTORY_SCREENSHOTS); 828 } 829 createHiddenDir()830 private File createHiddenDir() throws Exception { 831 File parentDir = new File(Environment.getExternalStorageDirectory(), 832 Environment.DIRECTORY_DOWNLOADS); 833 File dir = new File(parentDir, HIDDEN_DIR_NAME); 834 dir.mkdirs(); 835 File nomedia = new File(dir, ".nomedia"); 836 nomedia.createNewFile(); 837 838 MediaStore.scanFile(mIsolatedResolver, nomedia); 839 840 return dir; 841 } 842 deleteAllFilesNoThrow()843 private void deleteAllFilesNoThrow() { 844 try (Cursor c = mIsolatedResolver.query( 845 MediaStore.Files.getContentUri(VOLUME_EXTERNAL), 846 new String[] {MediaStore.MediaColumns.DATA}, null, null)) { 847 while(c.moveToNext()) { 848 (new File(c.getString( 849 c.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)))).delete(); 850 } 851 } 852 } 853 } 854