blob: b6eb8f316420c8884e8ee3971d18dafe785b96c0 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.photopicker.cts;
import static android.os.SystemProperties.getBoolean;
import static android.photopicker.cts.PickerProviderMediaGenerator.setCloudProvider;
import static android.photopicker.cts.PickerProviderMediaGenerator.syncCloudProvider;
import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
import static android.photopicker.cts.util.PhotoPickerUiUtils.REGEX_PACKAGE_NAME;
import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddButton;
import static android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback.PLAYBACK_STATE_READY;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.net.Uri;
import android.photopicker.cts.PickerProviderMediaGenerator.MediaGenerator;
import android.photopicker.cts.cloudproviders.CloudProviderPrimary;
import android.photopicker.cts.cloudproviders.CloudProviderPrimary.CloudMediaSurfaceControllerImpl;
import android.provider.MediaStore;
import android.util.Pair;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiObject;
import androidx.test.uiautomator.UiSelector;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* PhotoPicker test coverage for remote video preview APIs.
* End-to-end coverage for video preview controls is present in {@link PhotoPickerTest}
*/
@RunWith(AndroidJUnit4.class)
public class RemoteVideoPreviewTest extends PhotoPickerBaseTest {
private MediaGenerator mCloudPrimaryMediaGenerator;
private final List<Uri> mUriList = new ArrayList<>();
private static final String CLOUD_ID1 = "CLOUD_ID1";
private static final String CLOUD_ID2 = "CLOUD_ID2";
private static final String COLLECTION_1 = "COLLECTION_1";
private static final long IMAGE_SIZE_BYTES = 107684;
private static final long VIDEO_SIZE_BYTES = 135600;
private static final int VIDEO_PIXEL_FORMAT = PixelFormat.RGB_565;
private CloudMediaSurfaceControllerImpl mSurfaceControllerListener;
// This is required to assert the order in which the APIs are called.
private InOrder mAssertInOrder;
@Before
public void setUp() throws Exception {
super.setUp();
Assume.assumeTrue(getBoolean("sys.photopicker.pickerdb.enabled", true));
mDevice.executeShellCommand("setprop sys.photopicker.remote_preview true");
Assume.assumeTrue(getBoolean("sys.photopicker.remote_preview", true));
mCloudPrimaryMediaGenerator = PickerProviderMediaGenerator.getMediaGenerator(
mContext, CloudProviderPrimary.AUTHORITY);
mCloudPrimaryMediaGenerator.resetAll();
mCloudPrimaryMediaGenerator.setMediaCollectionId(COLLECTION_1);
setCloudProvider(mContext, CloudProviderPrimary.AUTHORITY);
assertThat(MediaStore.isCurrentCloudMediaProviderAuthority(mContext.getContentResolver(),
CloudProviderPrimary.AUTHORITY)).isTrue();
mSurfaceControllerListener = CloudProviderPrimary.getMockSurfaceControllerListener();
mAssertInOrder = Mockito.inOrder(mSurfaceControllerListener);
}
@After
public void tearDown() throws Exception {
for (Uri uri : mUriList) {
deleteMedia(uri, mContext);
}
mUriList.clear();
mActivity.finish();
setCloudProvider(mContext, null);
}
@Test
@Ignore("Re-enable once b/223224727 is fixed")
public void testBasicVideoPreview() throws Exception {
initCloudProviderWithVideo(Arrays.asList(Pair.create(null, CLOUD_ID1)));
launchPreviewMultiple(/* count */ 1);
final int surfaceId = 0;
verifyInitialVideoPreviewSetup(surfaceId, CLOUD_ID1);
// Remote Preview calls onMediaPlay when PLAYBACK_STATE_READY is sent by the
// CloudMediaProvider
verifyPlaybackStartedWhenPlayerReady(surfaceId);
// TODO(b/215187981): Add test for onMediaPause()
// Exit preview mode
mDevice.pressBack();
// Remote Preview calls onSurfaceDestroyed, check if the id is the same (as the
// CloudMediaProvider is only rendering to one surface id)
mAssertInOrder.verify(mSurfaceControllerListener).onSurfaceDestroyed(eq(surfaceId));
// Remote Preview calls onPlayerRelease() and onDestroy() for CMP to release the
// resources.
mAssertInOrder.verify(mSurfaceControllerListener).onPlayerRelease();
mAssertInOrder.verify(mSurfaceControllerListener).onDestroy();
final UiObject addButton = findAddButton();
addButton.click();
// We don't test the result of the picker here because the intention of the test is only to
// test the remote preview APIs
}
@Test
@Ignore("Re-enable once b/223224727 is fixed")
public void testSwipeAdjacentVideoPreview() throws Exception {
initCloudProviderWithVideo(
Arrays.asList(Pair.create(null, CLOUD_ID1), Pair.create(null, CLOUD_ID2)));
launchPreviewMultiple(/* count */ 2);
final int surfaceIdForFirstVideoPreview = 0;
verifyInitialVideoPreviewSetup(surfaceIdForFirstVideoPreview, CLOUD_ID2);
// Remote Preview calls onMediaPlay when PLAYBACK_STATE_READY is sent by the
// CloudMediaProvider
verifyPlaybackStartedWhenPlayerReady(surfaceIdForFirstVideoPreview);
// Swipe left preview mode
swipeLeftAndWait();
// Remote Preview calls onSurfaceCreated with monotonically increasing surfaceIds
final int surfaceIdForSecondVideoPreview = 1;
verifyAdjacentVideoSwipe(surfaceIdForFirstVideoPreview, surfaceIdForSecondVideoPreview,
CLOUD_ID1);
// Swipe right in preview mode and go to first video, but the surface id will have
// increased monotonically
swipeRightAndWait();
final int surfaceIdForThirdVideoPreview = 2;
verifyAdjacentVideoSwipe(surfaceIdForSecondVideoPreview, surfaceIdForThirdVideoPreview,
CLOUD_ID2);
final UiObject addButton = findPreviewAddButton();
addButton.click();
// We don't test the result of the picker here because the intention of the test is only to
// test the remote preview APIs
}
@Test
@Ignore("Re-enable once b/223224727 is fixed")
public void testSwipeImageVideoPreview() throws Exception {
initCloudProviderWithImage(Arrays.asList(Pair.create(null, CLOUD_ID1)));
initCloudProviderWithVideo(Arrays.asList(Pair.create(null, CLOUD_ID2)));
launchPreviewMultiple(/* count */ 2);
// Remote Preview calls onSurfaceCreated with monotonically increasing surfaceIds
int surfaceId = 0;
verifyInitialVideoPreviewSetup(surfaceId, CLOUD_ID2);
// Remote Preview calls onMediaPlay when PLAYBACK_STATE_READY is sent by the
// CloudMediaProvider
verifyPlaybackStartedWhenPlayerReady(surfaceId);
// Swipe left preview mode
swipeLeftAndWait();
mAssertInOrder.verify(mSurfaceControllerListener).onSurfaceDestroyed(eq(surfaceId));
// Remote Preview calls onPlayerRelease() for CMP to release the resources if there is no
// video to preview
mAssertInOrder.verify(mSurfaceControllerListener).onPlayerRelease();
// Swipe right preview mode
swipeRightAndWait();
// SurfaceId increases monotonically for each video preview
surfaceId++;
verifyInitialVideoPreviewSetup(surfaceId, CLOUD_ID2);
verifyPlaybackStartedWhenPlayerReady(surfaceId);
final UiObject addButton = findPreviewAddButton();
addButton.click();
// We don't test the result of the picker here because the intention of the test is only to
// test the remote preview APIs
}
/**
* Verify surface controller interactions on swiping from one video to another.
* Note: This test assumes that the first video is in playing state.
*
* @param oldSurfaceId the Surface ID which we are swiping away from
* @param newSurfaceId the Surface ID to which we are swiping
* @param newMediaId the media ID of the video we are swiping to
*/
private void verifyAdjacentVideoSwipe(int oldSurfaceId, int newSurfaceId, String newMediaId)
throws Exception {
mAssertInOrder.verify(mSurfaceControllerListener).onSurfaceCreated(eq(newSurfaceId),
any(), eq(newMediaId));
// The surface for the first video is destroyed when it is no longer visible on the screen
// (swipe is complete).
mAssertInOrder.verify(mSurfaceControllerListener).onSurfaceDestroyed(eq(oldSurfaceId));
verifyPlaybackStartedWhenPlayerReady(newSurfaceId);
}
/**
* The first time video preview is called, the surface controller object should get the
* following callbacks in the following order:
* * To prepare media player
* * Surface related callbacks (onSurfaceCreated and onSurfaceChanged)
*
* @param surfaceId Surface ID to set up video preview on
* @param mediaId Media ID to set up video preview with
*/
private void verifyInitialVideoPreviewSetup(int surfaceId, String mediaId) {
// Remote Preview calls onPlayerCreate as the first call to CloudMediaProvider
mAssertInOrder.verify(mSurfaceControllerListener).onPlayerCreate();
// Remote Preview calls onSurfaceCreated with surfaceId and mediaId as expected
mAssertInOrder.verify(mSurfaceControllerListener).onSurfaceCreated(eq(surfaceId), any(),
eq(mediaId));
// Remote Preview calls onSurfaceChanged to set the format, width and height
// corresponding to the video on the same surfaceId
mAssertInOrder.verify(mSurfaceControllerListener).onSurfaceChanged(eq(surfaceId),
eq(VIDEO_PIXEL_FORMAT), anyInt(), anyInt());
}
private void verifyPlaybackStartedWhenPlayerReady(int surfaceId) throws Exception {
CloudProviderPrimary.setPlaybackState(surfaceId, PLAYBACK_STATE_READY);
// Wait for photo picker to receive the event and invoke media play via binder calls.
MediaStore.waitForIdle(mContext.getContentResolver());
mAssertInOrder.verify(mSurfaceControllerListener).onMediaPlay(eq(surfaceId));
}
private void initCloudProviderWithImage(List<Pair<String, String>> mediaPairs)
throws Exception {
for (Pair<String, String> pair : mediaPairs) {
addImage(mCloudPrimaryMediaGenerator, pair.first, pair.second);
}
syncCloudProvider(mContext);
}
private void addImage(MediaGenerator generator, String localId, String cloudId)
throws Exception {
generator.addMedia(localId, cloudId, /* albumId */ null, "image/jpeg",
/* mimeTypeExtension */ 0, IMAGE_SIZE_BYTES, /* isFavorite */ false,
R.raw.lg_g4_iso_800_jpg);
}
private void initCloudProviderWithVideo(List<Pair<String, String>> mediaPairs)
throws Exception {
for (Pair<String, String> pair : mediaPairs) {
addVideo(mCloudPrimaryMediaGenerator, pair.first, pair.second);
}
syncCloudProvider(mContext);
}
private void addVideo(MediaGenerator generator, String localId, String cloudId)
throws Exception {
generator.addMedia(localId, cloudId, /* albumId */ null, "video/mp4",
/* mimeTypeExtension */ 0, VIDEO_SIZE_BYTES, /* isFavorite */ false,
R.raw.test_video);
}
private void launchPreviewMultiple(int count) throws Exception {
final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
mActivity.startActivityForResult(intent, REQUEST_CODE);
final List<UiObject> itemList = findItemList(count);
final int itemCount = itemList.size();
assertThat(itemCount).isEqualTo(count);
for (final UiObject item : itemList) {
item.click();
mDevice.waitForIdle();
}
final UiObject viewSelectedButton = findViewSelectedButton();
viewSelectedButton.click();
mDevice.waitForIdle();
// Wait for CloudMediaProvider binder calls to finish.
MediaStore.waitForIdle(mContext.getContentResolver());
}
private static UiObject findViewSelectedButton() {
return new UiObject(new UiSelector().resourceIdMatches(
REGEX_PACKAGE_NAME + ":id/button_view_selected"));
}
private void swipeLeftAndWait() throws Exception {
final int width = mDevice.getDisplayWidth();
final int height = mDevice.getDisplayHeight();
mDevice.swipe(width / 2, height / 2, width / 4, height / 2, 10);
mDevice.waitForIdle();
// Wait for CloudMediaProvider binder calls to finish.
MediaStore.waitForIdle(mContext.getContentResolver());
}
private void swipeRightAndWait() throws Exception {
final int width = mDevice.getDisplayWidth();
final int height = mDevice.getDisplayHeight();
mDevice.swipe(width / 4, height / 2, width / 2, height / 2, 10);
mDevice.waitForIdle();
// Wait for CloudMediaProvider binder calls to finish.
MediaStore.waitForIdle(mContext.getContentResolver());
}
}