• 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 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