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 android.photopicker.cts; 18 19 import static android.photopicker.cts.util.GetContentActivityAliasUtils.clearPackageData; 20 import static android.photopicker.cts.util.GetContentActivityAliasUtils.getDocumentsUiPackageName; 21 import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImageWithUnknownMimeType; 22 import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImagesAndGetUris; 23 import static android.photopicker.cts.util.PhotoPickerFilesUtils.createMj2VideosAndGetUris; 24 import static android.photopicker.cts.util.PhotoPickerFilesUtils.createMpegVideo; 25 import static android.photopicker.cts.util.PhotoPickerFilesUtils.createSvgImage; 26 import static android.photopicker.cts.util.PhotoPickerFilesUtils.createVideoWithUnknownMimeType; 27 import static android.photopicker.cts.util.PhotoPickerFilesUtils.createVideosAndGetUris; 28 import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia; 29 import static android.photopicker.cts.util.PhotoPickerUiUtils.REGEX_PACKAGE_NAME; 30 import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT; 31 import static android.photopicker.cts.util.PhotoPickerUiUtils.clickAndWait; 32 import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton; 33 import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList; 34 import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddButton; 35 import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddOrSelectButton; 36 import static android.photopicker.cts.util.ResultsAssertionsUtils.assertContainsMimeType; 37 import static android.photopicker.cts.util.ResultsAssertionsUtils.assertExtension; 38 import static android.photopicker.cts.util.ResultsAssertionsUtils.assertMimeType; 39 import static android.photopicker.cts.util.ResultsAssertionsUtils.assertPersistedGrant; 40 import static android.photopicker.cts.util.ResultsAssertionsUtils.assertPickerUriFormat; 41 import static android.photopicker.cts.util.ResultsAssertionsUtils.assertRedactedReadOnlyAccess; 42 43 import static com.google.common.truth.Truth.assertThat; 44 import static com.google.common.truth.Truth.assertWithMessage; 45 46 import android.content.ClipData; 47 import android.content.Intent; 48 import android.media.AudioAttributes; 49 import android.media.AudioFocusRequest; 50 import android.media.AudioManager; 51 import android.net.Uri; 52 import android.photopicker.cts.util.GetContentActivityAliasUtils; 53 import android.provider.MediaStore; 54 55 import androidx.test.uiautomator.UiObject; 56 import androidx.test.uiautomator.UiObjectNotFoundException; 57 import androidx.test.uiautomator.UiSelector; 58 59 import org.junit.After; 60 import org.junit.Before; 61 import org.junit.Ignore; 62 import org.junit.Test; 63 import org.junit.runner.RunWith; 64 import org.junit.runners.Parameterized; 65 import org.junit.runners.Parameterized.Parameter; 66 import org.junit.runners.Parameterized.Parameters; 67 68 import java.util.ArrayList; 69 import java.util.Arrays; 70 import java.util.List; 71 import java.util.Map; 72 73 /** 74 * Photo Picker Device only tests for common flows. 75 */ 76 @RunWith(Parameterized.class) 77 public class PhotoPickerTest extends PhotoPickerBaseTest { 78 79 @Parameter(0) 80 public String mAction; 81 82 @Parameters(name = "intent={0}") data()83 public static Iterable<? extends Object> data() { 84 return getTestParameters(); 85 } 86 87 private List<Uri> mUriList = new ArrayList<>(); 88 89 private static int sGetContentTakeOverActivityAliasState; 90 91 @Before setUp()92 public void setUp() throws Exception { 93 super.setUp(); 94 95 sGetContentTakeOverActivityAliasState = GetContentActivityAliasUtils.enableAndGetOldState(); 96 clearPackageData(getDocumentsUiPackageName()); 97 } 98 99 @After tearDown()100 public void tearDown() throws Exception { 101 for (Uri uri : mUriList) { 102 deleteMedia(uri, mContext); 103 } 104 mUriList.clear(); 105 106 if (mActivity != null) { 107 mActivity.finish(); 108 } 109 110 GetContentActivityAliasUtils.restoreState(sGetContentTakeOverActivityAliasState); 111 } 112 113 @Test testSingleSelect()114 public void testSingleSelect() throws Exception { 115 final int itemCount = 1; 116 mUriList.addAll(createImagesAndGetUris(itemCount, mContext.getUserId())); 117 118 final Intent intent = new Intent(mAction); 119 launchPhotoPickerForIntent(intent); 120 121 final UiObject item = findItemList(itemCount).get(0); 122 clickAndWait(sDevice, item); 123 124 final Uri uri = mActivity.getResult().data.getData(); 125 assertPickerUriFormat(uri, mContext.getUserId()); 126 assertPersistedGrant(uri, mContext.getContentResolver()); 127 assertRedactedReadOnlyAccess(uri); 128 } 129 130 @Test testSingleSelectForFavoritesAlbum()131 public void testSingleSelectForFavoritesAlbum() throws Exception { 132 final int itemCount = 1; 133 mUriList.addAll(createImagesAndGetUris(itemCount, mContext.getUserId(), 134 /* isFavorite */ true)); 135 136 final Intent intent = new Intent(mAction); 137 launchPhotoPickerForIntent(intent); 138 139 UiObject albumsTab = sDevice.findObject(new UiSelector().text( 140 "Albums")); 141 clickAndWait(sDevice, albumsTab); 142 final UiObject album = findItemList(1).get(0); 143 clickAndWait(sDevice, album); 144 145 final UiObject item = findItemList(itemCount).get(0); 146 clickAndWait(sDevice, item); 147 148 final Uri uri = mActivity.getResult().data.getData(); 149 assertPickerUriFormat(uri, mContext.getUserId()); 150 assertRedactedReadOnlyAccess(uri); 151 } 152 153 @Test testLaunchPreviewMultipleForVideoAlbum()154 public void testLaunchPreviewMultipleForVideoAlbum() throws Exception { 155 final int videoCount = 2; 156 mUriList.addAll(createVideosAndGetUris(videoCount, mContext.getUserId())); 157 158 Intent intent = new Intent(mAction); 159 intent.setType("video/*"); 160 addMultipleSelectionFlag(intent); 161 launchPhotoPickerForIntent(intent); 162 163 UiObject albumsTab = sDevice.findObject(new UiSelector().text( 164 "Albums")); 165 clickAndWait(sDevice, albumsTab); 166 final UiObject album = findItemList(1).get(0); 167 clickAndWait(sDevice, album); 168 169 final List<UiObject> itemList = findItemList(videoCount); 170 final int itemCount = itemList.size(); 171 172 assertThat(itemCount).isEqualTo(videoCount); 173 174 for (int i = 0; i < itemCount; i++) { 175 clickAndWait(sDevice, itemList.get(i)); 176 } 177 178 clickAndWait(sDevice, findViewSelectedButton()); 179 180 // Wait for playback to start. This is needed in some devices where playback 181 // buffering -> ready state takes around 10s. 182 final long playbackStartTimeout = 10000; 183 (findPreviewVideoImageView()).waitUntilGone(playbackStartTimeout); 184 } 185 186 @Test testSingleSelectWithPreview()187 public void testSingleSelectWithPreview() throws Exception { 188 final int itemCount = 1; 189 mUriList.addAll(createImagesAndGetUris(itemCount, mContext.getUserId())); 190 191 final Intent intent = new Intent(mAction); 192 launchPhotoPickerForIntent(intent); 193 194 final UiObject item = findItemList(itemCount).get(0); 195 item.longClick(); 196 sDevice.waitForIdle(); 197 198 final UiObject addButton = findPreviewAddOrSelectButton(); 199 assertThat(addButton.waitForExists(1000)).isTrue(); 200 clickAndWait(sDevice, addButton); 201 202 final Uri uri = mActivity.getResult().data.getData(); 203 assertPickerUriFormat(uri, mContext.getUserId()); 204 assertRedactedReadOnlyAccess(uri); 205 } 206 207 @Test testMultiSelect()208 public void testMultiSelect() throws Exception { 209 final int imageCount = 4; 210 mUriList.addAll(createImagesAndGetUris(imageCount, mContext.getUserId())); 211 Intent intent = new Intent(mAction); 212 addMultipleSelectionFlag(intent); 213 launchPhotoPickerForIntent(intent); 214 215 final List<UiObject> itemList = findItemList(imageCount); 216 final int itemCount = itemList.size(); 217 assertThat(itemCount).isEqualTo(imageCount); 218 for (int i = 0; i < itemCount; i++) { 219 clickAndWait(sDevice, itemList.get(i)); 220 } 221 222 clickAndWait(sDevice, findAddButton()); 223 224 final ClipData clipData = mActivity.getResult().data.getClipData(); 225 final int count = clipData.getItemCount(); 226 assertThat(count).isEqualTo(itemCount); 227 for (int i = 0; i < count; i++) { 228 final Uri uri = clipData.getItemAt(i).getUri(); 229 assertPickerUriFormat(uri, mContext.getUserId()); 230 assertPersistedGrant(uri, mContext.getContentResolver()); 231 assertRedactedReadOnlyAccess(uri); 232 } 233 } 234 235 @Test testMultiSelect_longPress()236 public void testMultiSelect_longPress() throws Exception { 237 final int videoCount = 3; 238 mUriList.addAll(createMj2VideosAndGetUris(videoCount, mContext.getUserId())); 239 240 Intent intent = new Intent(mAction); 241 intent.setType("video/*"); 242 addMultipleSelectionFlag(intent); 243 launchPhotoPickerForIntent(intent); 244 245 final List<UiObject> itemList = findItemList(videoCount); 246 final int itemCount = itemList.size(); 247 assertThat(itemCount).isEqualTo(videoCount); 248 249 // Select one item from Photo grid 250 clickAndWait(sDevice, itemList.get(0)); 251 252 // Preview the item 253 UiObject item = itemList.get(1); 254 item.longClick(); 255 sDevice.waitForIdle(); 256 257 final UiObject addOrSelectButton = findPreviewAddOrSelectButton(); 258 assertWithMessage("Timed out waiting for AddOrSelectButton to appear") 259 .that(addOrSelectButton.waitForExists(1000)).isTrue(); 260 261 // Select the item from Preview 262 clickAndWait(sDevice, addOrSelectButton); 263 264 sDevice.pressBack(); 265 266 // Select one more item from Photo grid 267 clickAndWait(sDevice, itemList.get(2)); 268 269 clickAndWait(sDevice, findAddButton()); 270 271 // Verify that all 3 items are returned 272 final ClipData clipData = mActivity.getResult().data.getClipData(); 273 final int count = clipData.getItemCount(); 274 assertThat(count).isEqualTo(3); 275 for (int i = 0; i < count; i++) { 276 final Uri uri = clipData.getItemAt(i).getUri(); 277 assertPickerUriFormat(uri, mContext.getUserId()); 278 assertPersistedGrant(uri, mContext.getContentResolver()); 279 assertRedactedReadOnlyAccess(uri); 280 } 281 } 282 283 @Test testMultiSelect_preview()284 public void testMultiSelect_preview() throws Exception { 285 final int imageCount = 4; 286 mUriList.addAll(createImagesAndGetUris(imageCount, mContext.getUserId())); 287 288 Intent intent = new Intent(mAction); 289 addMultipleSelectionFlag(intent); 290 launchPhotoPickerForIntent(intent); 291 292 final List<UiObject> itemList = findItemList(imageCount); 293 final int itemCount = itemList.size(); 294 assertThat(itemCount).isEqualTo(imageCount); 295 for (int i = 0; i < itemCount; i++) { 296 clickAndWait(sDevice, itemList.get(i)); 297 } 298 299 clickAndWait(sDevice, findViewSelectedButton()); 300 301 // Swipe left three times 302 swipeLeftAndWait(); 303 swipeLeftAndWait(); 304 swipeLeftAndWait(); 305 306 // Deselect one item 307 clickAndWait(sDevice, findPreviewSelectedCheckButton()); 308 309 // Return selected items 310 clickAndWait(sDevice, findPreviewAddButton()); 311 312 final ClipData clipData = mActivity.getResult().data.getClipData(); 313 final int count = clipData.getItemCount(); 314 assertThat(count).isEqualTo(itemCount - 1); 315 for (int i = 0; i < count; i++) { 316 final Uri uri = clipData.getItemAt(i).getUri(); 317 assertPickerUriFormat(uri, mContext.getUserId()); 318 assertPersistedGrant(uri, mContext.getContentResolver()); 319 assertRedactedReadOnlyAccess(uri); 320 } 321 } 322 323 @Test 324 @Ignore("Re-enable once we find work around for b/226318844") testMultiSelect_previewVideoPlayPause()325 public void testMultiSelect_previewVideoPlayPause() throws Exception { 326 launchPreviewMultipleWithVideos(/* videoCount */ 3); 327 328 // Check Play/Pause in first video 329 testVideoPreviewPlayPause(); 330 331 // Move to third video 332 swipeLeftAndWait(); 333 swipeLeftAndWait(); 334 // Check Play/Pause in third video 335 testVideoPreviewPlayPause(); 336 337 // We don't test the result of the picker here because the intention of the test is only to 338 // test the video controls 339 } 340 341 @Test testMultiSelect_previewVideoMuteButtonInitial()342 public void testMultiSelect_previewVideoMuteButtonInitial() throws Exception { 343 launchPreviewMultipleWithVideos(/* videoCount */ 1); 344 345 final UiObject playPauseButton = findPlayPauseButton(); 346 final UiObject muteButton = findMuteButton(); 347 348 // check that player controls are visible 349 assertPlayerControlsVisible(playPauseButton, muteButton); 350 351 // Test 1: Initial state of the mute Button 352 // Check that initial state of mute button is mute, i.e., volume off 353 assertMuteButtonState(muteButton, /* isMuted */ true); 354 355 // Test 2: Click Mute Button 356 // Click to unmute the audio 357 clickAndWait(sDevice, muteButton); 358 359 waitForBinderCallsToComplete(); 360 361 // Check that mute button state is unmute, i.e., it shows `volume up` icon 362 assertMuteButtonState(muteButton, /* isMuted */ false); 363 // Click on the muteButton and check that mute button status is now 'mute' 364 clickAndWait(sDevice, muteButton); 365 366 waitForBinderCallsToComplete(); 367 368 assertMuteButtonState(muteButton, /* isMuted */ true); 369 // Click on the muteButton and check that mute button status is now unmute 370 clickAndWait(sDevice, muteButton); 371 372 waitForBinderCallsToComplete(); 373 374 assertMuteButtonState(muteButton, /* isMuted */ false); 375 376 // Test 3: Next preview resumes mute state 377 // Go back and launch preview again 378 sDevice.pressBack(); 379 clickAndWait(sDevice, findViewSelectedButton()); 380 381 waitForBinderCallsToComplete(); 382 383 // check that player controls are visible 384 assertPlayerControlsVisible(playPauseButton, muteButton); 385 assertMuteButtonState(muteButton, /* isMuted */ false); 386 387 // We don't test the result of the picker here because the intention of the test is only to 388 // test the video controls 389 } 390 391 @Test testMultiSelect_previewVideoMuteButtonOnSwipe()392 public void testMultiSelect_previewVideoMuteButtonOnSwipe() throws Exception { 393 launchPreviewMultipleWithVideos(/* videoCount */ 3); 394 395 final UiObject playPauseButton = findPlayPauseButton(); 396 final UiObject muteButton = findMuteButton(); 397 final UiObject playerView = findPlayerView(); 398 399 // check that player controls are visible 400 assertPlayerControlsVisible(playPauseButton, muteButton); 401 402 // Test 1: Swipe resumes mute state, with state of the button is 'volume off' / 'mute' 403 assertMuteButtonState(muteButton, /* isMuted */ true); 404 // Swipe to next page and check that muteButton is in mute state. 405 swipeLeftAndWait(); 406 407 waitForBinderCallsToComplete(); 408 409 // set-up and wait for player controls to be sticky 410 setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton); 411 assertMuteButtonState(muteButton, /* isMuted */ true); 412 413 // Test 2: Swipe resumes mute state, with state of mute button 'volume up' / 'unmute' 414 // Click muteButton again to check the next video resumes the previous video's mute state 415 clickAndWait(sDevice, muteButton); 416 417 waitForBinderCallsToComplete(); 418 419 assertMuteButtonState(muteButton, /* isMuted */ false); 420 // check that next video resumed previous video's mute state 421 swipeLeftAndWait(); 422 423 waitForBinderCallsToComplete(); 424 425 // Wait for 1s before checking Play/Pause button's visibility 426 playPauseButton.waitForExists(1000); 427 // check that player controls are visible 428 assertPlayerControlsVisible(playPauseButton, muteButton); 429 assertMuteButtonState(muteButton, /* isMuted */ false); 430 431 // We don't test the result of the picker here because the intention of the test is only to 432 // test the video controls 433 } 434 435 @Test testVideoPreviewAudioFocus()436 public void testVideoPreviewAudioFocus() throws Exception { 437 final int[] focusStateForTest = new int[1]; 438 final AudioManager audioManager = mContext.getSystemService(AudioManager.class); 439 AudioFocusRequest audioFocusRequest = 440 new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT) 441 .setAudioAttributes(new AudioAttributes.Builder() 442 .setContentType(AudioAttributes.USAGE_MEDIA) 443 .setUsage(AudioAttributes.CONTENT_TYPE_MOVIE) 444 .build()) 445 .setWillPauseWhenDucked(true) 446 .setAcceptsDelayedFocusGain(false) 447 .setOnAudioFocusChangeListener(focusChange -> { 448 if (focusChange == AudioManager.AUDIOFOCUS_LOSS 449 || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT 450 || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { 451 focusStateForTest[0] = focusChange; 452 } 453 }) 454 .build(); 455 456 // Request AudioFocus 457 assertWithMessage("Expected requestAudioFocus result") 458 .that(audioManager.requestAudioFocus(audioFocusRequest)) 459 .isEqualTo(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); 460 461 // Launch Preview 462 launchPreviewMultipleWithVideos(/* videoCount */ 2); 463 // Video preview launches in mute mode, hence, test's audio focus shouldn't be lost when 464 // video preview starts 465 assertThat(focusStateForTest[0]).isEqualTo(0); 466 467 final UiObject muteButton = findMuteButton(); 468 // unmute the audio of video preview 469 clickAndWait(sDevice, muteButton); 470 471 // Remote video preview involves binder calls 472 // Wait for Binder calls to complete and device to be idle 473 MediaStore.waitForIdle(mContext.getContentResolver()); 474 sDevice.waitForIdle(); 475 476 assertMuteButtonState(muteButton, /* isMuted */ false); 477 478 // Verify that test lost the audio focus because PhotoPicker has requested audio focus now. 479 assertThat(focusStateForTest[0]).isEqualTo(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT); 480 481 // Reset the focusStateForTest to verify test loses audio focus when video preview is 482 // launched with unmute state 483 focusStateForTest[0] = 0; 484 // Abandon the audio focus before requesting again. This is necessary to reduce test flakes 485 audioManager.abandonAudioFocusRequest(audioFocusRequest); 486 // Request AudioFocus from test again 487 assertWithMessage("Expected requestAudioFocus result") 488 .that(audioManager.requestAudioFocus(audioFocusRequest)) 489 .isEqualTo(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); 490 491 // Wait for PhotoPicker to lose Audio Focus 492 findPlayButton().waitForExists(SHORT_TIMEOUT); 493 // Test requesting audio focus will make PhotoPicker lose audio focus, Verify video is 494 // paused when PhotoPicker loses audio focus. 495 assertWithMessage("PlayPause button's content description") 496 .that(findPlayPauseButton().getContentDescription()) 497 .isEqualTo("Play"); 498 499 // Swipe to next video and verify preview gains audio focus 500 swipeLeftAndWait(); 501 findPauseButton().waitForExists(SHORT_TIMEOUT); 502 // Video preview is now in unmute mode. Hence, PhotoPicker will request audio focus. Verify 503 // that test lost the audio focus. 504 assertThat(focusStateForTest[0]).isEqualTo(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT); 505 } 506 507 @Test 508 @Ignore("Re-enable once we find work around for b/226318844") testMultiSelect_previewVideoControlsVisibility()509 public void testMultiSelect_previewVideoControlsVisibility() throws Exception { 510 launchPreviewMultipleWithVideos(/* videoCount */ 3); 511 512 final UiObject playPauseButton = findPlayPauseButton(); 513 final UiObject muteButton = findMuteButton(); 514 // Check that buttons auto hide. 515 assertPlayerControlsAutoHide(playPauseButton, muteButton); 516 517 final UiObject playerView = findPlayerView(); 518 // Click on StyledPlayerView to make the video controls visible 519 clickAndWait(sDevice, playerView); 520 assertPlayerControlsVisible(playPauseButton, muteButton); 521 522 // Wait for 1s and check that controls are still visible 523 assertPlayerControlsDontAutoHide(playPauseButton, muteButton); 524 525 // Click on StyledPlayerView and check that controls are no longer visible. Don't click in 526 // the center, clicking in the center may pause the video. 527 playerView.clickBottomRight(); 528 sDevice.waitForIdle(); 529 assertPlayerControlsHidden(playPauseButton, muteButton); 530 531 // Swipe left and check that controls are not visible 532 swipeLeftAndWait(); 533 assertPlayerControlsHidden(playPauseButton, muteButton); 534 535 // Click on the StyledPlayerView and check that controls appear 536 clickAndWait(sDevice, playerView); 537 assertPlayerControlsVisible(playPauseButton, muteButton); 538 539 // Swipe left to check that controls are now visible on swipe 540 swipeLeftAndWait(); 541 assertPlayerControlsVisible(playPauseButton, muteButton); 542 543 // Check that the player controls are auto hidden in 1s 544 assertPlayerControlsAutoHide(playPauseButton, muteButton); 545 546 // We don't test the result of the picker here because the intention of the test is only to 547 // test the video controls 548 } 549 550 @Test testMimeTypeFilter()551 public void testMimeTypeFilter() throws Exception { 552 final int videoCount = 2; 553 mUriList.addAll(createMj2VideosAndGetUris(videoCount, mContext.getUserId())); 554 final int imageCount = 1; 555 mUriList.addAll(createImagesAndGetUris(imageCount, mContext.getUserId())); 556 557 final String mimeType = "video/mj2"; 558 559 Intent intent = new Intent(mAction); 560 addMultipleSelectionFlag(intent); 561 intent.setType(mimeType); 562 launchPhotoPickerForIntent(intent); 563 564 // find all items 565 final List<UiObject> itemList = findItemList(-1); 566 final int itemCount = itemList.size(); 567 assertThat(itemCount).isAtLeast(videoCount); 568 for (int i = 0; i < itemCount; i++) { 569 clickAndWait(sDevice, itemList.get(i)); 570 } 571 572 clickAndWait(sDevice, findAddButton()); 573 574 final ClipData clipData = mActivity.getResult().data.getClipData(); 575 final int count = clipData.getItemCount(); 576 assertThat(count).isEqualTo(itemCount); 577 for (int i = 0; i < count; i++) { 578 final Uri uri = clipData.getItemAt(i).getUri(); 579 assertPickerUriFormat(uri, mContext.getUserId()); 580 assertPersistedGrant(uri, mContext.getContentResolver()); 581 assertRedactedReadOnlyAccess(uri); 582 assertMimeType(uri, mimeType); 583 } 584 } 585 586 @Test testExtraMimeTypeFilter()587 public void testExtraMimeTypeFilter() throws Exception { 588 final int mj2VideoCount = 2; 589 // Creates 2 videos with mime type: "video/mj2" 590 mUriList.addAll(createMj2VideosAndGetUris(mj2VideoCount, mContext.getUserId())); 591 592 final int mp4VideoCount = 3; 593 // Creates 3 videos with mime type: "video/mp4" 594 mUriList.addAll(createVideosAndGetUris(mp4VideoCount, mContext.getUserId())); 595 596 final int imageCount = 4; 597 // Creates 4 images with mime type: "image/dng" 598 mUriList.addAll(createImagesAndGetUris(imageCount, mContext.getUserId())); 599 600 Intent intent = new Intent(mAction); 601 addMultipleSelectionFlag(intent); 602 603 if (Intent.ACTION_GET_CONTENT.equals(intent.getAction())) { 604 intent.setType("*/*"); 605 } 606 final String[] mimeTypes = new String[]{"video/mj2", "image/dng"}; 607 intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); 608 launchPhotoPickerForIntent(intent); 609 610 final int totalCount = mj2VideoCount + imageCount; 611 final List<UiObject> itemList = findItemList(totalCount); 612 final int itemCount = itemList.size(); 613 assertThat(itemCount).isAtLeast(totalCount); 614 for (int i = 0; i < itemCount; i++) { 615 clickAndWait(sDevice, itemList.get(i)); 616 } 617 618 clickAndWait(sDevice, findAddButton()); 619 620 final ClipData clipData = mActivity.getResult().data.getClipData(); 621 assertWithMessage("Expected number of items returned to be: " + itemCount) 622 .that(clipData.getItemCount()).isEqualTo(itemCount); 623 for (int i = 0; i < itemCount; i++) { 624 final Uri uri = clipData.getItemAt(i).getUri(); 625 assertPickerUriFormat(uri, mContext.getUserId()); 626 assertPersistedGrant(uri, mContext.getContentResolver()); 627 assertRedactedReadOnlyAccess(uri); 628 assertContainsMimeType(uri, mimeTypes); 629 } 630 } 631 632 @Test testMimeTypeFilterPriority()633 public void testMimeTypeFilterPriority() throws Exception { 634 final int videoCount = 2; 635 mUriList.addAll(createMj2VideosAndGetUris(videoCount, mContext.getUserId())); 636 final int imageCount = 1; 637 mUriList.addAll(createImagesAndGetUris(imageCount, mContext.getUserId())); 638 639 Intent intent = new Intent(mAction); 640 addMultipleSelectionFlag(intent); 641 // setType has lower priority than EXTRA_MIME_TYPES filters. 642 intent.setType("image/*"); 643 final String mimeType = "video/mj2"; 644 intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {mimeType}); 645 launchPhotoPickerForIntent(intent); 646 647 // find all items 648 final List<UiObject> itemList = findItemList(-1); 649 final int itemCount = itemList.size(); 650 assertThat(itemCount).isAtLeast(videoCount); 651 for (int i = 0; i < itemCount; i++) { 652 clickAndWait(sDevice, itemList.get(i)); 653 } 654 655 clickAndWait(sDevice, findAddButton()); 656 657 final ClipData clipData = mActivity.getResult().data.getClipData(); 658 assertWithMessage("Expected number of items returned to be: " + itemCount) 659 .that(clipData.getItemCount()).isEqualTo(itemCount); 660 for (int i = 0; i < itemCount; i++) { 661 final Uri uri = clipData.getItemAt(i).getUri(); 662 assertPickerUriFormat(uri, mContext.getUserId()); 663 assertPersistedGrant(uri, mContext.getContentResolver()); 664 assertRedactedReadOnlyAccess(uri); 665 assertMimeType(uri, mimeType); 666 } 667 } 668 669 @Test testPickerUriFileExtensions()670 public void testPickerUriFileExtensions() throws Exception { 671 // 1. Create test media items 672 mUriList.add(createSvgImage(mContext.getUserId())); 673 mUriList.add(createImageWithUnknownMimeType(mContext.getUserId())); 674 mUriList.add(createMpegVideo(mContext.getUserId())); 675 mUriList.add(createVideoWithUnknownMimeType(mContext.getUserId())); 676 677 final int expectedItemCount = mUriList.size(); 678 679 final Map<String, String> mimeTypeToExpectedExtensionMap = Map.of( 680 "image/svg+xml", "svg", 681 "image/foo", "jpg", 682 "video/mpeg", "mpeg", 683 "video/foo", "mp4" 684 ); 685 686 // 2. Launch Picker in multi-select mode for the test mime types 687 final Intent intent = new Intent(mAction); 688 addMultipleSelectionFlag(intent); 689 launchPhotoPickerForIntent(intent); 690 691 // 3. Add all items 692 final List<UiObject> itemList = findItemList(expectedItemCount); 693 final int itemCount = itemList.size(); 694 assertWithMessage("Unexpected number of media items found in the picker ui") 695 .that(itemCount) 696 .isEqualTo(expectedItemCount); 697 698 for (UiObject item : itemList) { 699 clickAndWait(sDevice, item); 700 } 701 clickAndWait(sDevice, findAddButton()); 702 703 // 4. Get the activity result data to extract the picker uris 704 final ClipData clipData = mActivity.getResult().data.getClipData(); 705 assertWithMessage("Unexpected number of items returned from the picker activity") 706 .that(clipData.getItemCount()) 707 .isEqualTo(itemCount); 708 709 // 5. Assert the picker uri file extension as expected for each item 710 for (int i = 0; i < itemCount; i++) { 711 final Uri uri = clipData.getItemAt(i).getUri(); 712 assertExtension(uri, mimeTypeToExpectedExtensionMap); 713 } 714 } 715 assertMuteButtonState(UiObject muteButton, boolean isMuted)716 private void assertMuteButtonState(UiObject muteButton, boolean isMuted) 717 throws UiObjectNotFoundException { 718 // We use content description to assert the state of the mute button, there is no other way 719 // to test this. 720 final String expectedContentDescription = isMuted ? "Unmute video" : "Mute video"; 721 final String assertMessage = 722 "Expected mute button content description to be " + expectedContentDescription; 723 assertWithMessage(assertMessage).that(muteButton.getContentDescription()) 724 .isEqualTo(expectedContentDescription); 725 } 726 testVideoPreviewPlayPause()727 private void testVideoPreviewPlayPause() throws Exception { 728 final UiObject playPauseButton = findPlayPauseButton(); 729 final UiObject muteButton = findMuteButton(); 730 731 // Wait for buttons to auto hide. 732 assertPlayerControlsAutoHide(playPauseButton, muteButton); 733 734 // Click on StyledPlayerView to make the video controls visible 735 clickAndWait(sDevice, findPlayerView()); 736 737 // PlayPause button is now pause button, click the button to pause the video. 738 clickAndWait(sDevice, playPauseButton); 739 740 // Wait for 1s and check that play button is not auto hidden 741 assertPlayerControlsDontAutoHide(playPauseButton, muteButton); 742 743 // PlayPause button is now play button, click the button to play the video. 744 clickAndWait(sDevice, playPauseButton); 745 // Check that pause button auto-hides in 1s. 746 assertPlayerControlsAutoHide(playPauseButton, muteButton); 747 } 748 launchPreviewMultipleWithVideos(int videoCount)749 private void launchPreviewMultipleWithVideos(int videoCount) throws Exception { 750 mUriList.addAll(createVideosAndGetUris(videoCount, mContext.getUserId())); 751 752 Intent intent = new Intent(mAction); 753 intent.setType("video/*"); 754 addMultipleSelectionFlag(intent); 755 launchPhotoPickerForIntent(intent); 756 757 final List<UiObject> itemList = findItemList(videoCount); 758 final int itemCount = itemList.size(); 759 760 assertThat(itemCount).isEqualTo(videoCount); 761 762 for (int i = 0; i < itemCount; i++) { 763 clickAndWait(sDevice, itemList.get(i)); 764 } 765 766 clickAndWait(sDevice, findViewSelectedButton()); 767 768 // Wait for playback to start. This is needed in some devices where playback 769 // buffering -> ready state takes around 10s. 770 final long playbackStartTimeout = 10000; 771 (findPreviewVideoImageView()).waitUntilGone(playbackStartTimeout); 772 773 waitForBinderCallsToComplete(); 774 } 775 waitForBinderCallsToComplete()776 private void waitForBinderCallsToComplete() { 777 // Wait for Binder calls to complete and device to be idle 778 MediaStore.waitForIdle(mContext.getContentResolver()); 779 sDevice.waitForIdle(); 780 } 781 setUpAndAssertStickyPlayerControls(UiObject playerView, UiObject playPauseButton, UiObject muteButton)782 private void setUpAndAssertStickyPlayerControls(UiObject playerView, UiObject playPauseButton, 783 UiObject muteButton) throws Exception { 784 // Wait for 1s for player view to exist 785 playerView.waitForExists(1000); 786 // Wait for 1s or Play/Pause button to hide 787 playPauseButton.waitUntilGone(1000); 788 // Click on StyledPlayerView to make the video controls visible 789 clickAndWait(sDevice, playerView); 790 assertPlayerControlsVisible(playPauseButton, muteButton); 791 } 792 assertPlayerControlsVisible(UiObject playPauseButton, UiObject muteButton)793 private void assertPlayerControlsVisible(UiObject playPauseButton, UiObject muteButton) { 794 assertVisible(playPauseButton, "Expected play/pause button to be visible"); 795 assertVisible(muteButton, "Expected mute button to be visible"); 796 } 797 assertPlayerControlsHidden(UiObject playPauseButton, UiObject muteButton)798 private void assertPlayerControlsHidden(UiObject playPauseButton, UiObject muteButton) { 799 assertHidden(playPauseButton, "Expected play/pause button to be hidden"); 800 assertHidden(muteButton, "Expected mute button to be hidden"); 801 } 802 assertPlayerControlsAutoHide(UiObject playPauseButton, UiObject muteButton)803 private void assertPlayerControlsAutoHide(UiObject playPauseButton, UiObject muteButton) { 804 // These buttons should auto hide in 1 second after the video playback start. Since we can't 805 // identify the video playback start time, we wait for 2 seconds instead. 806 assertWithMessage("Expected play/pause button to auto hide in 2s") 807 .that(playPauseButton.waitUntilGone(2000)).isTrue(); 808 assertHidden(muteButton, "Expected mute button to hide after 2s"); 809 } 810 assertPlayerControlsDontAutoHide(UiObject playPauseButton, UiObject muteButton)811 private void assertPlayerControlsDontAutoHide(UiObject playPauseButton, UiObject muteButton) { 812 assertWithMessage("Expected play/pause button to not auto hide in 1s") 813 .that(playPauseButton.waitUntilGone(1100)).isFalse(); 814 assertVisible(muteButton, "Expected mute button to be still visible after 1s"); 815 } 816 assertVisible(UiObject button, String message)817 private void assertVisible(UiObject button, String message) { 818 assertWithMessage(message).that(button.exists()).isTrue(); 819 } 820 assertHidden(UiObject button, String message)821 private void assertHidden(UiObject button, String message) { 822 assertWithMessage(message).that(button.exists()).isFalse(); 823 } 824 findViewSelectedButton()825 private static UiObject findViewSelectedButton() { 826 return new UiObject(new UiSelector().resourceIdMatches( 827 REGEX_PACKAGE_NAME + ":id/button_view_selected")); 828 } 829 findPreviewSelectedCheckButton()830 private static UiObject findPreviewSelectedCheckButton() { 831 return new UiObject(new UiSelector().resourceIdMatches( 832 REGEX_PACKAGE_NAME + ":id/preview_selected_check_button")); 833 } 834 835 findPlayerView()836 private static UiObject findPlayerView() { 837 return new UiObject(new UiSelector().resourceIdMatches( 838 REGEX_PACKAGE_NAME + ":id/preview_player_view")); 839 } 840 findMuteButton()841 private static UiObject findMuteButton() { 842 return new UiObject(new UiSelector().resourceIdMatches( 843 REGEX_PACKAGE_NAME + ":id/preview_mute")); 844 } 845 findPlayPauseButton()846 private static UiObject findPlayPauseButton() { 847 return new UiObject(new UiSelector().resourceIdMatches( 848 REGEX_PACKAGE_NAME + ":id/exo_play_pause")); 849 } 850 findPauseButton()851 private static UiObject findPauseButton() { 852 return new UiObject(new UiSelector().descriptionContains("Pause")); 853 } 854 findPlayButton()855 private static UiObject findPlayButton() { 856 return new UiObject(new UiSelector().descriptionContains("Play")); 857 } 858 findPreviewVideoImageView()859 private static UiObject findPreviewVideoImageView() { 860 return new UiObject(new UiSelector().resourceIdMatches( 861 REGEX_PACKAGE_NAME + ":id/preview_video_image")); 862 } 863 swipeLeftAndWait()864 private void swipeLeftAndWait() { 865 final int width = sDevice.getDisplayWidth(); 866 final int height = sDevice.getDisplayHeight(); 867 sDevice.swipe(15 * width / 20, height / 2, width / 20, height / 2, 10); 868 sDevice.waitForIdle(); 869 } 870 getTestParameters()871 private static List<String> getTestParameters() { 872 return Arrays.asList( 873 MediaStore.ACTION_PICK_IMAGES, 874 Intent.ACTION_GET_CONTENT 875 ); 876 } 877 addMultipleSelectionFlag(Intent intent)878 private void addMultipleSelectionFlag(Intent intent) { 879 switch (intent.getAction()) { 880 case MediaStore.ACTION_PICK_IMAGES: 881 intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 882 MediaStore.getPickImagesMaxLimit()); 883 break; 884 case Intent.ACTION_GET_CONTENT: 885 intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); 886 break; 887 default: 888 // do nothing 889 } 890 } 891 launchPhotoPickerForIntent(Intent intent)892 private void launchPhotoPickerForIntent(Intent intent) throws Exception { 893 // GET_CONTENT needs to have setType 894 if (Intent.ACTION_GET_CONTENT.equals(intent.getAction()) && intent.getType() == null) { 895 intent.setType("*/*"); 896 intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"}); 897 } 898 899 mActivity.startActivityForResult(intent, REQUEST_CODE); 900 } 901 } 902