diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-02-05 13:00:29 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-02-05 13:00:29 +0000 |
commit | eb5f9c6be33d6fc717d6f7f61d66c2f26ae0ba47 (patch) | |
tree | 3a7f0ed5b670f0d99caf733c2f298c298d6bc485 | |
parent | 49d5297c12d827fdd640cc05c71c672845506c43 (diff) | |
parent | a5fd7706fc67110511362d5dacee26de4b1060d0 (diff) | |
download | cts-android12-mainline-media-release.tar.gz |
Snap for 8152310 from a5fd7706fc67110511362d5dacee26de4b1060d0 to mainline-media-releaseandroid-mainline-12.0.0_r89android12-mainline-media-release
Change-Id: Id7f08b5288431ea58bcea0a56d6a8cd15bca6084
20 files changed, 703 insertions, 135 deletions
diff --git a/hostsidetests/appcompat/strictjavapackages/Android.bp b/hostsidetests/appcompat/strictjavapackages/Android.bp index fcb20e829cd..9ab8a832ca3 100644 --- a/hostsidetests/appcompat/strictjavapackages/Android.bp +++ b/hostsidetests/appcompat/strictjavapackages/Android.bp @@ -33,6 +33,6 @@ java_test_host { test_suites: [ "cts", "general-tests", - "mts", + "mts-mainline-infra", ], } diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java index b2dc9b816ac..e44a04ae2b0 100644 --- a/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java +++ b/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java @@ -41,6 +41,7 @@ import androidx.test.uiautomator.Until; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeNotNull; /** Basic sample for unbundled UiAutomator. */ @RunWith(AndroidJUnit4.class) @@ -111,7 +112,7 @@ public class DeviceTest { mContext.startActivity(intent); UiObject2 view = waitForView(By.text(Constants.TEST_APP_PACKAGE)); - assertNotNull("Activity under-test was not launched or found!", view); + assumeNotNull("Activity under-test was not launched or found!", view); Log.d(LOG_TAG, "Started Activity under-test."); } diff --git a/tests/MediaProviderTranscode/Android.bp b/tests/MediaProviderTranscode/Android.bp index 4ba3243e177..0a6dfd66ac8 100644 --- a/tests/MediaProviderTranscode/Android.bp +++ b/tests/MediaProviderTranscode/Android.bp @@ -7,6 +7,7 @@ android_test { test_suites: [ "device-tests", "cts", + "mts-mediaprovider", ], compile_multilib: "both", @@ -30,6 +31,7 @@ android_test { "truth-prebuilt", ], + min_sdk_version: "30", certificate: "media", java_resources: [":CtsTranscodeTestAppSupportsHevc", ":CtsTranscodeTestAppSupportsSlowMotion"] } @@ -45,6 +47,7 @@ android_test_helper_app { ], static_libs: ["androidx.legacy_legacy-support-v4"], target_sdk_version: "28", + min_sdk_version: "30", } android_test_helper_app { @@ -58,4 +61,5 @@ android_test_helper_app { ], static_libs: ["androidx.legacy_legacy-support-v4"], target_sdk_version: "28", + min_sdk_version: "30", } diff --git a/tests/PhotoPicker/Android.bp b/tests/PhotoPicker/Android.bp index 37b049b36c2..5a9fc331708 100644 --- a/tests/PhotoPicker/Android.bp +++ b/tests/PhotoPicker/Android.bp @@ -22,7 +22,7 @@ android_test { test_config: "AndroidTest.xml", srcs: ["src/**/*.java", "helper/**/*.java", ":CtsProviderTestUtils",], compile_multilib: "both", - test_suites: ["device-tests", "mts-mediaprovider", "cts"], + test_suites: ["general-tests", "mts-mediaprovider", "cts"], sdk_version: "core_current", min_sdk_version: "30", libs: [ diff --git a/tests/PhotoPicker/AndroidTest.xml b/tests/PhotoPicker/AndroidTest.xml index dbd1bc41e5c..d0bf7973c0b 100644 --- a/tests/PhotoPicker/AndroidTest.xml +++ b/tests/PhotoPicker/AndroidTest.xml @@ -20,9 +20,14 @@ <option name="test-file-name" value="CtsPhotoPickerTest.apk" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> - <option name="force-root" value="false" /> + <option name="force-root" value="true" /> </target_preparer> + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + <option name="test-suite-tag" value="cts" /> <option name="test-tag" value="PhotoPickerTests" /> <!-- Instant apps cannot access external storage --> diff --git a/tests/PhotoPicker/res/raw/test_video.mp4 b/tests/PhotoPicker/res/raw/test_video.mp4 Binary files differnew file mode 100644 index 00000000000..ab95ac07dd3 --- /dev/null +++ b/tests/PhotoPicker/res/raw/test_video.mp4 diff --git a/tests/PhotoPicker/res/raw/testvideo_meta.mp4 b/tests/PhotoPicker/res/raw/test_video_dng.mp4 Binary files differindex 9b38f0e8e7d..9b38f0e8e7d 100644 --- a/tests/PhotoPicker/res/raw/testvideo_meta.mp4 +++ b/tests/PhotoPicker/res/raw/test_video_dng.mp4 diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java index 49a1acbd772..5873feed179 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java @@ -25,6 +25,8 @@ import android.provider.DeviceConfig; import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; +import com.android.modules.utils.build.SdkLevel; + import org.junit.Before; /** @@ -41,6 +43,7 @@ public class PhotoPickerBaseTest { @Before public void setUp() throws Exception { final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); + mDevice = UiDevice.getInstance(inst); enablePhotoPickerFlag(inst); @@ -49,7 +52,6 @@ public class PhotoPickerBaseTest { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // Wake up the device and dismiss the keyguard before the test starts - mDevice = UiDevice.getInstance(inst); mDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP"); mDevice.executeShellCommand("wm dismiss-keyguard"); @@ -60,13 +62,17 @@ public class PhotoPickerBaseTest { mDevice.waitForIdle(); } - private void enablePhotoPickerFlag(Instrumentation inst) { - inst.getUiAutomation().adoptShellPermissionIdentity( - Manifest.permission.WRITE_DEVICE_CONFIG); - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, - "picker_intent_enabled" /* name */, - "true" /* value */, - false /* makeDefault */); + private void enablePhotoPickerFlag(Instrumentation inst) throws Exception { + if (SdkLevel.isAtLeastS()) { + inst.getUiAutomation().adoptShellPermissionIdentity( + Manifest.permission.WRITE_DEVICE_CONFIG); + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + "picker_intent_enabled" /* name */, + "true" /* value */, + false /* makeDefault */); + } else { + mDevice.executeShellCommand("setprop persist.sys.storage_picker_enabled true"); + } } } diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java index 10587786202..c05d22e2d46 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java @@ -31,7 +31,6 @@ import android.content.Intent; import android.net.Uri; import android.provider.MediaStore; -import androidx.test.filters.SdkSuppress; import androidx.test.uiautomator.UiObject; import com.android.bedstead.harrier.BedsteadJUnit4; @@ -53,7 +52,6 @@ import java.util.List; * Photo Picker Device only tests for cross profile interaction flows. */ @RunWith(BedsteadJUnit4.class) -@SdkSuppress(minSdkVersion = 31, codeName = "S") public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest { @ClassRule @Rule public static final DeviceState sDeviceState = new DeviceState(); @@ -70,7 +68,6 @@ public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest { @Test @RequireRunOnWorkProfile - @Ignore("Enable after b/201323670 is fixed") public void testWorkApp_canAccessPersonalProfileContents() throws Exception { final int imageCount = 2; createImages(imageCount, sDeviceState.primaryUser().id(), mUriList); @@ -109,7 +106,7 @@ public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest { @Test @EnsureHasWorkProfile - @Ignore("Enable after b/201323670 is fixed") + @Ignore("Enable after b/216475844 is fixed") public void testPersonalApp_canAccessWorkProfileContents() throws Exception { final int imageCount = 2; createImages(imageCount, sDeviceState.workProfile().id(), mUriList); diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java index 9b5904cefde..8d26dd8abbc 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java @@ -19,11 +19,12 @@ package android.photopicker.cts; import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertMimeType; import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat; import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess; +import static android.photopicker.cts.util.PhotoPickerFilesUtils.createDNGVideos; import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages; import static android.photopicker.cts.util.PhotoPickerFilesUtils.createVideos; import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia; -import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT; import static android.photopicker.cts.util.PhotoPickerUiUtils.REGEX_PACKAGE_NAME; +import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT; import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton; import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList; import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddButton; @@ -38,7 +39,6 @@ import android.content.Intent; import android.net.Uri; import android.provider.MediaStore; -import androidx.test.filters.SdkSuppress; import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.UiObject; import androidx.test.uiautomator.UiSelector; @@ -54,7 +54,6 @@ import java.util.List; * Photo Picker Device only tests for common flows. */ @RunWith(AndroidJUnit4.class) -@SdkSuppress(minSdkVersion = 31, codeName = "S") public class PhotoPickerTest extends PhotoPickerBaseTest { private List<Uri> mUriList = new ArrayList<>(); @@ -75,8 +74,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { mActivity.startActivityForResult(intent, REQUEST_CODE); final UiObject item = findItemList(itemCount).get(0); - item.click(); - mDevice.waitForIdle(); + clickAndWait(item); final Uri uri = mActivity.getResult().data.getData(); assertPickerUriFormat(uri, mContext.getUserId()); @@ -96,8 +94,8 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { mDevice.waitForIdle(); final UiObject addButton = findPreviewAddOrSelectButton(); - addButton.click(); - mDevice.waitForIdle(); + assertThat(addButton.waitForExists(1000)).isTrue(); + clickAndWait(addButton); final Uri uri = mActivity.getResult().data.getData(); assertPickerUriFormat(uri, mContext.getUserId()); @@ -137,9 +135,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { assertThat(itemCount).isEqualTo(imageCount); // Select maxCount + 1 item for (int i = 0; i < itemCount; i++) { - final UiObject item = itemList.get(i); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(i)); } UiObject snackbarTextView = mDevice.findObject(new UiSelector().text( @@ -150,9 +146,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { assertWithMessage("Timed out waiting for snackbar to disappear").that( snackbarTextView.waitUntilGone(SHORT_TIMEOUT)).isTrue(); - final UiObject addButton = findAddButton(); - addButton.click(); - mDevice.waitForIdle(); + clickAndWait(findAddButton()); final ClipData clipData = mActivity.getResult().data.getClipData(); final int count = clipData.getItemCount(); @@ -171,9 +165,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { final int itemCount = itemList.size(); assertThat(itemCount).isEqualTo(imageCount); // Select 1 item - final UiObject item = itemList.get(0); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(0)); final Uri uri = mActivity.getResult().data.getData(); assertPickerUriFormat(uri, mContext.getUserId()); @@ -193,14 +185,10 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { final int itemCount = itemList.size(); assertThat(itemCount).isEqualTo(imageCount); for (int i = 0; i < itemCount; i++) { - final UiObject item = itemList.get(i); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(i)); } - final UiObject addButton = findAddButton(); - addButton.click(); - mDevice.waitForIdle(); + clickAndWait(findAddButton()); final ClipData clipData = mActivity.getResult().data.getClipData(); final int count = clipData.getItemCount(); @@ -215,7 +203,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { @Test public void testMultiSelect_longPress() throws Exception { final int videoCount = 3; - createVideos(videoCount, mContext.getUserId(), mUriList); + createDNGVideos(videoCount, mContext.getUserId(), mUriList); final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit() intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100); @@ -227,27 +215,26 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { assertThat(itemCount).isEqualTo(videoCount); // Select one item from Photo grid - itemList.get(0).click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(0)); + // Preview the item UiObject item = itemList.get(1); item.longClick(); mDevice.waitForIdle(); + final UiObject addOrSelectButton = findPreviewAddOrSelectButton(); + assertWithMessage("Timed out waiting for AddOrSelectButton to appear") + .that(addOrSelectButton.waitForExists(1000)).isTrue(); + // Select the item from Preview - final UiObject selectButton = findPreviewAddOrSelectButton(); - selectButton.click(); - mDevice.waitForIdle(); + clickAndWait(addOrSelectButton); mDevice.pressBack(); // Select one more item from Photo grid - itemList.get(2).click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(2)); - final UiObject addButton = findAddButton(); - addButton.click(); - mDevice.waitForIdle(); + clickAndWait(findAddButton()); // Verify that all 3 items are returned final ClipData clipData = mActivity.getResult().data.getClipData(); @@ -273,31 +260,21 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { final int itemCount = itemList.size(); assertThat(itemCount).isEqualTo(imageCount); for (int i = 0; i < itemCount; i++) { - final UiObject item = itemList.get(i); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(i)); } - final UiObject viewSelectedButton = findViewSelectedButton(); - viewSelectedButton.click(); - mDevice.waitForIdle(); + clickAndWait(findViewSelectedButton()); // Swipe left three times - swipeLeft(); - mDevice.waitForIdle(); - swipeLeft(); - mDevice.waitForIdle(); - swipeLeft(); - mDevice.waitForIdle(); + swipeLeftAndWait(); + swipeLeftAndWait(); + swipeLeftAndWait(); // Deselect one item - final UiObject selectCheckButton = findPreviewSelectCheckButton(); - selectCheckButton.click(); - mDevice.waitForIdle(); + clickAndWait(findPreviewSelectCheckButton()); - final UiObject addButton = findPreviewAddButton(); - addButton.click(); - mDevice.waitForIdle(); + // Return selected items + clickAndWait(findPreviewAddButton()); final ClipData clipData = mActivity.getResult().data.getClipData(); final int count = clipData.getItemCount(); @@ -310,11 +287,145 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { } @Test + public void testMultiSelect_PreviewVideoPlayPause() throws Exception { + launchPreviewMultipleWithVideos(/* videoCount */ 4); + + // Check Play/Pause in first video + testVideoPreviewPlayPause(); + + // Move to second video + swipeLeftAndWait(); + // Check Play/Pause in second video + testVideoPreviewPlayPause(); + + // Move to fourth video + swipeLeftAndWait(); + swipeLeftAndWait(); + // Check Play/Pause in fourth video + testVideoPreviewPlayPause(); + + 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 video controls + } + + @Test + public void testMultiSelect_PreviewVideoMuteButton() throws Exception { + launchPreviewMultipleWithVideos(/* videoCount */ 4); + + final UiObject playPauseButton = findPlayPauseButton(); + final UiObject muteButton = findMuteButton(); + final UiObject playerView = findPlayerView(); + + // set-up and wait for player controls to be sticky + setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton); + + // Test 1: Initial state of the mute Button + // Check that initial state of mute button is `selected`, i.e., volume off + assertThat(muteButton.isSelected()).isTrue(); + + // Test 2: Click Mute Button + // Click to unmute the audio + clickAndWait(muteButton); + // Check that mute button state is `not selected`, i.e., it shows `volume up` icon + assertThat(muteButton.isSelected()).isFalse(); + // Click on the muteButton and check that mute button status is now `selected` + clickAndWait(muteButton); + assertThat(muteButton.isSelected()).isTrue(); + + // Test 3: Swipe resumes mute state, with state of mute button = `not selected` + // Click muteButton again to check the next video resumes the previous video's mute state + clickAndWait(muteButton); + assertThat(muteButton.isSelected()).isFalse(); + // check that next video resumed previous video's mute state + swipeLeftAndWait(); + // set-up and wait for player controls to be sticky + setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton); + assertThat(muteButton.isSelected()).isFalse(); + + // Test 4: Swipe resumes mute state, with state of the button = `selected` + // Click muteButton again to check the next video resumes the previous video's mute state + clickAndWait(muteButton); + assertThat(muteButton.isSelected()).isTrue(); + // Swipe to next page and check that muteButton is selected + swipeLeftAndWait(); + // set-up and wait for player controls to be sticky + setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton); + assertThat(muteButton.isSelected()).isTrue(); + + // Test 5: Next preview resumes mute state + // Click muteButton again to check if next Preview launch resumes the muteButton state + clickAndWait(muteButton); + assertThat(muteButton.isSelected()).isFalse(); + // Go back and launch preview again + mDevice.pressBack(); + // set-up and wait for player controls to be sticky + clickAndWait(findViewSelectedButton()); + setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton); + assertThat(muteButton.isSelected()).isFalse(); + + clickAndWait(findPreviewAddButton()); + // We don't test the result of the picker here because the intention of the test is only to + // test the video controls + } + + @Test + public void testMultiSelect_PreviewVideoControlsVisibility() throws Exception { + launchPreviewMultipleWithVideos(/* videoCount */ 3); + + mDevice.waitForIdle(); + + final UiObject playPauseButton = findPlayPauseButton(); + final UiObject muteButton = findMuteButton(); + // Check that the player controls are visible + assertPlayerControlsVisible(playPauseButton, muteButton); + + // Check that buttons auto hide. + assertPlayerControlsAutoHide(playPauseButton, muteButton); + + final UiObject playerView = findPlayerView(); + // Click on StyledPlayerView to make the video controls visible + clickAndWait(playerView); + assertPlayerControlsVisible(playPauseButton, muteButton); + + // Wait for 1s and check that controls are still visible + assertPlayerControlsDontAutoHide(playPauseButton, muteButton); + + // Click on StyledPlayerView and check that controls are no longer visible. Don't click in + // the center, clicking in the center may pause the video. + playerView.clickBottomRight(); + mDevice.waitForIdle(); + assertPlayerControlsHidden(playPauseButton, muteButton); + + // Swipe left and check that controls are not visible + swipeLeftAndWait(); + assertPlayerControlsHidden(playPauseButton, muteButton); + + // Click on the StyledPlayerView and check that controls appear + clickAndWait(playerView); + assertPlayerControlsVisible(playPauseButton, muteButton); + + // Swipe left to check that controls are now visible on swipe + swipeLeftAndWait(); + assertPlayerControlsVisible(playPauseButton, muteButton); + + // Check that the player controls are auto hidden in 1s + assertPlayerControlsAutoHide(playPauseButton, muteButton); + + 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 video controls + } + + @Test public void testMimeTypeFilter() throws Exception { final int videoCount = 2; - createVideos(videoCount, mContext.getUserId(), mUriList); + createDNGVideos(videoCount, mContext.getUserId(), mUriList); final int imageCount = 1; createImages(imageCount, mContext.getUserId(), mUriList); + final String mimeType = "video/dng"; final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); @@ -328,14 +439,10 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { final int itemCount = itemList.size(); assertThat(itemCount).isAtLeast(videoCount); for (int i = 0; i < itemCount; i++) { - final UiObject item = itemList.get(i); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(i)); } - final UiObject addButton = findAddButton(); - addButton.click(); - mDevice.waitForIdle(); + clickAndWait(findAddButton()); final ClipData clipData = mActivity.getResult().data.getClipData(); final int count = clipData.getItemCount(); @@ -348,6 +455,89 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { } } + private void testVideoPreviewPlayPause() throws Exception { + final UiObject playPauseButton = findPlayPauseButton(); + final UiObject muteButton = findMuteButton(); + + // Wait for buttons to auto hide. + assertPlayerControlsAutoHide(playPauseButton, muteButton); + + // Click on StyledPlayerView to make the video controls visible + clickAndWait(findPlayerView()); + + // PlayPause button is now pause button, click the button to pause the video. + clickAndWait(playPauseButton); + + // Wait for 1s and check that play button is not auto hidden + assertPlayerControlsDontAutoHide(playPauseButton, muteButton); + + // PlayPause button is now play button, click the button to play the video. + clickAndWait(playPauseButton); + // Check that pause button auto-hides in 1s. + assertPlayerControlsAutoHide(playPauseButton, muteButton); + } + + private void launchPreviewMultipleWithVideos(int videoCount) throws Exception { + createVideos(videoCount, mContext.getUserId(), mUriList); + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit() + intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100); + intent.setType("video/*"); + mActivity.startActivityForResult(intent, REQUEST_CODE); + + final List<UiObject> itemList = findItemList(videoCount); + final int itemCount = itemList.size(); + + assertThat(itemCount).isEqualTo(videoCount); + + for (int i = 0; i < itemCount; i++) { + clickAndWait(itemList.get(i)); + } + + clickAndWait(findViewSelectedButton()); + } + + private void setUpAndAssertStickyPlayerControls(UiObject playerView, UiObject playPauseButton, + UiObject muteButton) throws Exception { + // Check that buttons auto hide. + assertPlayerControlsAutoHide(playPauseButton, muteButton); + // Click on StyledPlayerView to make the video controls visible + clickAndWait(playerView); + assertPlayerControlsVisible(playPauseButton, muteButton); + } + + private void assertPlayerControlsVisible(UiObject playPauseButton, UiObject muteButton) { + assertVisible(playPauseButton, "Expected play/pause button to be visible"); + assertVisible(muteButton, "Expected mute button to be visible"); + } + + private void assertPlayerControlsHidden(UiObject playPauseButton, UiObject muteButton) { + assertHidden(playPauseButton, "Expected play/pause button to be hidden"); + assertHidden(muteButton, "Expected mute button to be hidden"); + } + + private void assertPlayerControlsAutoHide(UiObject playPauseButton, UiObject muteButton) { + // These buttons should auto hide in 1 second after the video playback start. Since we can't + // identify the video playback start time, we wait for 2 seconds instead. + assertWithMessage("Expected play/pause button to auto hide in 2s") + .that(playPauseButton.waitUntilGone(2000)).isTrue(); + assertHidden(muteButton, "Expected mute button to hide after 2s"); + } + + private void assertPlayerControlsDontAutoHide(UiObject playPauseButton, UiObject muteButton) { + assertWithMessage("Expected play/pause button to not auto hide in 1s") + .that(playPauseButton.waitUntilGone(1100)).isFalse(); + assertVisible(muteButton, "Expected mute button to be still visible after 1s"); + } + + private void assertVisible(UiObject button, String message) { + assertWithMessage(message).that(button.exists()).isTrue(); + } + + private void assertHidden(UiObject button, String message) { + assertWithMessage(message).that(button.exists()).isFalse(); + } + private static UiObject findViewSelectedButton() { return new UiObject(new UiSelector().resourceIdMatches( REGEX_PACKAGE_NAME + ":id/button_view_selected")); @@ -358,9 +548,31 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { REGEX_PACKAGE_NAME + ":id/preview_select_check_button")); } - private void swipeLeft() { + + private static UiObject findPlayerView() { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/preview_player_view")); + } + + private static UiObject findMuteButton() { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/preview_mute")); + } + + private static UiObject findPlayPauseButton() { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/exo_play_pause")); + } + + private void clickAndWait(UiObject uiObject) throws Exception { + uiObject.click(); + mDevice.waitForIdle(); + } + + private void swipeLeftAndWait() { final int width = mDevice.getDisplayWidth(); final int height = mDevice.getDisplayHeight(); - mDevice.swipe(width / 2, height / 2, width / 4, height / 2, 10); + mDevice.swipe(15 * width / 20, height / 2, width / 20, height / 2, 20); + mDevice.waitForIdle(); } } diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java index e0385ca89c9..d9ff84bed99 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java @@ -17,9 +17,7 @@ package android.photopicker.cts.util; import static android.os.SystemProperties.getBoolean; -import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE; -import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO; -import static android.provider.MediaStore.Files.FileColumns.MIME_TYPE; +import static android.provider.MediaStore.Files.FileColumns; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -65,16 +63,22 @@ public class PhotoPickerAssertionsUtils { public static void assertRedactedReadOnlyAccess(Uri uri) throws Exception { assertThat(uri).isNotNull(); // TODO(b/205291616): Replace FileColumns.MIME_TYPE with PickerMediaColumns.MIME_TYPE - final String[] projection = new String[]{ MIME_TYPE }; + final String[] projection = new String[]{ FileColumns.MIME_TYPE }; final Context context = InstrumentationRegistry.getTargetContext(); final ContentResolver resolver = context.getContentResolver(); - final Cursor c = resolver.query(uri, projection, null, null); - assertThat(c).isNotNull(); - assertThat(c.moveToFirst()).isTrue(); + try (Cursor c = resolver.query(uri, projection, null, null)) { + assertThat(c).isNotNull(); + assertThat(c.moveToFirst()).isTrue(); + + final String mimeType; + if (getBoolean("sys.photopicker.pickerdb.enabled", true)) { + // TODO(b/205291616): Replace FileColumns.MIME_TYPE with + // PickerMediaColumns.MIME_TYPE + mimeType = c.getString(c.getColumnIndex(FileColumns.MIME_TYPE)); + } else { + mimeType = c.getString(c.getColumnIndex(FileColumns.MIME_TYPE)); + } - if (getBoolean("sys.photopicker.pickerdb.enabled", true)) { - // TODO(b/205291616): Replace FileColumns.MIME_TYPE with PickerMediaColumns.MIME_TYPE - final String mimeType = c.getString(c.getColumnIndex(MIME_TYPE)); if (mimeType.startsWith("image")) { assertImageRedactedReadOnlyAccess(uri, resolver); } else if (mimeType.startsWith("video")) { @@ -82,24 +86,14 @@ public class PhotoPickerAssertionsUtils { } else { fail("The mime type is not as expected: " + mimeType); } - } else { - final int mediaType = c.getInt(1); - switch (mediaType) { - case MEDIA_TYPE_IMAGE: - assertImageRedactedReadOnlyAccess(uri, resolver); - break; - case MEDIA_TYPE_VIDEO: - assertVideoRedactedReadOnlyAccess(uri, resolver); - break; - default: - fail("The media type is not as expected: " + mediaType); - } } } private static void assertVideoRedactedReadOnlyAccess(Uri uri, ContentResolver resolver) throws Exception { // The location is redacted + // TODO(b/201505595): Make this method work for test_video.mp4. Currently it works only for + // test_video_dng.mp4 try (InputStream in = resolver.openInputStream(uri); ByteArrayOutputStream out = new ByteArrayOutputStream()) { FileUtils.copy(in, out); diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java index 37a44f99915..8103b70f6cd 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java @@ -49,6 +49,15 @@ public class PhotoPickerFilesUtils { } } + public static void createDNGVideos(int count, int userId, List<Uri> uriList) + throws Exception { + for (int i = 0; i < count; i++) { + final Uri uri = createDNGVideo(userId); + uriList.add(uri); + clearMediaOwner(uri, userId); + } + } + public static void createVideos(int count, int userId, List<Uri> uriList) throws Exception { for (int i = 0; i < count; i++) { @@ -69,8 +78,14 @@ public class PhotoPickerFilesUtils { ShellUtils.runShellCommand(cmd); } + private static Uri createDNGVideo(int userId) throws Exception { + final Uri uri = stageMedia(R.raw.test_video_dng, + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId); + return uri; + } + private static Uri createVideo(int userId) throws Exception { - final Uri uri = stageMedia(R.raw.testvideo_meta, + final Uri uri = stageMedia(R.raw.test_video, MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId); return uri; } diff --git a/tests/providerui/AndroidManifest.xml b/tests/providerui/AndroidManifest.xml index 2f1f791d9d8..a14df70d71b 100644 --- a/tests/providerui/AndroidManifest.xml +++ b/tests/providerui/AndroidManifest.xml @@ -23,9 +23,7 @@ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <!-- @@ -42,7 +40,7 @@ </intent> </queries> - <application android:requestLegacyExternalStorage = "true"> + <application> <uses-library android:name="android.test.runner"/> <activity android:name="android.providerui.cts.GetResultActivity" /> diff --git a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java index 4342810763c..47fe16104d4 100644 --- a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java +++ b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java @@ -26,12 +26,12 @@ import android.app.Activity; import android.app.Instrumentation; import android.app.UiAutomation; import android.content.ContentResolver; +import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.UriPermission; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; import android.os.Environment; @@ -39,7 +39,6 @@ import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; -import android.os.UserManager; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.provider.cts.ProviderTestUtils; @@ -50,12 +49,12 @@ import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.UiObjectNotFoundException; -import android.support.test.uiautomator.UiScrollable; import android.support.test.uiautomator.UiSelector; import android.support.test.uiautomator.Until; import android.system.Os; import android.text.format.DateUtils; import android.util.Log; +import android.util.Pair; import androidx.test.InstrumentationRegistry; @@ -68,6 +67,7 @@ import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -171,7 +171,7 @@ public class MediaStoreUiTest { } @Test - public void testGetDocumentUri_ThrowsWithoutPermission() throws Exception { + public void testGetDocumentUri_throwsWithoutPermission() throws Exception { if (!supportsHardware()) return; prepareFile(); @@ -185,7 +185,7 @@ public class MediaStoreUiTest { } @Test - public void testGetDocumentUri_Symmetry_ExternalStorageProvider() throws Exception { + public void testGetDocumentUri_symmetry_externalStorageProvider() throws Exception { if (!supportsHardware()) return; prepareFile(); @@ -207,10 +207,10 @@ public class MediaStoreUiTest { } @Test - public void testGetMediaUriAccess_MediaDocumentsProvider() throws Exception { + public void testGetMediaUriAccess_mediaDocumentsProvider() throws Exception { if (!supportsHardware()) return; - prepareFile(); + prepareFile("TEST"); clearDocumentsUi(); final Intent intent = new Intent(); intent.setAction(Intent.ACTION_OPEN_DOCUMENT); @@ -228,6 +228,104 @@ public class MediaStoreUiTest { assertAccessToMediaUri(mediaUri, mFile); } + @Test + public void testOpenFile_onMediaDocumentsProvider_success() throws Exception { + if (!supportsHardware()) return; + + final String rawText = "TEST"; + // Stage a text file which contains raw text "TEST" + prepareFile(rawText); + clearDocumentsUi(); + final Intent intent = new Intent(); + intent.setAction(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + mActivity.startActivityForResult(intent, REQUEST_CODE); + mDevice.waitForIdle(); + + findDocument(mFile.getName()).click(); + final Result result = mActivity.getResult(); + final Uri uri = result.data.getData(); + assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY, uri.getAuthority()); + + // Test reading + final byte[] expected = rawText.getBytes(); + final byte[] actual = new byte[4]; + try (ParcelFileDescriptor fd = mContext.getContentResolver() + .openFileDescriptor(uri, "r")) { + Os.read(fd.getFileDescriptor(), actual, 0, actual.length); + assertArrayEquals(expected, actual); + } + + // Test write and read after it + final byte[] writtenText = "Hello World".getBytes(); + final byte[] readText = new byte[11]; + try (ParcelFileDescriptor fd = mContext.getContentResolver() + .openFileDescriptor(uri, "wt")) { + Os.write(fd.getFileDescriptor(), writtenText, 0, writtenText.length); + } + try (ParcelFileDescriptor fd = mContext.getContentResolver() + .openFileDescriptor(uri, "r")) { + Os.read(fd.getFileDescriptor(), readText, 0, readText.length); + assertArrayEquals(writtenText, readText); + } + } + + @Test + public void testOpenFile_onMediaDocumentsProvider_failsWithoutAccess() throws Exception { + if (!supportsHardware()) return; + + clearDocumentsUi(); + final Intent intent = new Intent(); + intent.setAction(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + mActivity.startActivityForResult(intent, REQUEST_CODE); + mDevice.waitForIdle(); + + String rawText = "TEST"; + // Read and write grants will be provided to the file associated with this pair. + // Stages a text file which contains raw text "TEST" + Pair<Uri, File> uriFilePairWithGrants = prepareFileAndFetchDetails(rawText); + // Read and write grants will not be provided to the file associated with this pair + // Stages a text file which contains raw text "TEST" + Pair<Uri, File> uriFilePairWithoutGrants = prepareFileAndFetchDetails(rawText); + // Get access grants + findDocument(uriFilePairWithGrants.second.getName()).click(); + final Result result = mActivity.getResult(); + final Uri docUriOfFileWithAccess = result.data.getData(); + // Creating doc URI for file by string replacement + Uri docUriOfFileWithoutAccess = Uri.parse(docUriOfFileWithAccess.toSafeString().replaceAll( + String.valueOf(ContentUris.parseId(uriFilePairWithGrants.first)), + String.valueOf(ContentUris.parseId(uriFilePairWithoutGrants.first)))); + + try { + assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY, docUriOfFileWithAccess.getAuthority()); + assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY, + docUriOfFileWithoutAccess.getAuthority()); + // Test reading + try (ParcelFileDescriptor fd = mContext.getContentResolver().openFileDescriptor( + docUriOfFileWithoutAccess, "r")) { + fail("Expecting security exception as file does not have read grants which " + + "are provided through ACTION_OPEN_DOCUMENT intent."); + } catch (SecurityException expected) { + // Expected security exception as file does not have read grants + } + // Test writing + try (ParcelFileDescriptor fd = mContext.getContentResolver().openFileDescriptor( + docUriOfFileWithoutAccess, "wt")) { + fail("Expecting security exception as file does not have write grants which " + + "are provided through ACTION_OPEN_DOCUMENT intent."); + } catch (SecurityException expected) { + // Expected security exception as file does not have write grants + } + } finally { + // Deleting files + uriFilePairWithGrants.second.delete(); + uriFilePairWithoutGrants.second.delete(); + } + } + private void assertAccessToMediaUri(Uri mediaUri, File file) { final String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME}; try (Cursor c = mContext.getContentResolver().query( @@ -320,6 +418,28 @@ public class MediaStoreUiTest { Log.v(TAG, "Staged " + mFile + " as " + mMediaStoreUri); } + private void prepareFile(String rawText) throws Exception { + final File dir = new File(getVolumePath(mVolumeName), + Environment.DIRECTORY_DOCUMENTS); + final File file = new File(dir, "cts" + System.nanoTime() + ".txt"); + + mFile = stageFileWithRawText(rawText, file); + mMediaStoreUri = MediaStore.scanFile(mContext.getContentResolver(), mFile); + + Log.v(TAG, "Staged " + mFile + " as " + mMediaStoreUri); + } + + private Pair<Uri, File> prepareFileAndFetchDetails(String rawText) throws Exception { + final File dir = new File(getVolumePath(mVolumeName), Environment.DIRECTORY_DOCUMENTS); + final File file = new File(dir, "cts" + System.nanoTime() + ".txt"); + + File stagedFile = stageFileWithRawText(rawText, file); + + Uri uri = MediaStore.scanFile(mContext.getContentResolver(), stagedFile); + Log.v(TAG, "Staged " + stagedFile + " as " + uri); + return Pair.create(uri, stagedFile); + } + private void assertToolbarTitleEquals(String targetPackageName, String label) throws UiObjectNotFoundException { final UiSelector toolbarUiSelector = new UiSelector().resourceId( @@ -427,32 +547,28 @@ public class MediaStoreUiTest { // The caller may be trying to stage into a location only available to // the shell user, so we need to perform the entire copy as the shell final Context context = InstrumentationRegistry.getTargetContext(); - UserManager userManager = context.getSystemService(UserManager.class); - if (userManager.isSystemUser() && - FileUtils.contains(Environment.getStorageDirectory(), file)) { - executeShellCommand("mkdir -p " + file.getParent()); - - try (AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId)) { - final File source = ParcelFileDescriptor.getFile(afd.getFileDescriptor()); - final long skip = afd.getStartOffset(); - final long count = afd.getLength(); - - executeShellCommand(String.format("dd bs=1 if=%s skip=%d count=%d of=%s", - source.getAbsolutePath(), skip, count, file.getAbsolutePath())); + final File dir = file.getParentFile(); + dir.mkdirs(); + if (!dir.exists()) { + throw new FileNotFoundException("Failed to create parent for " + file); + } + try (InputStream source = context.getResources().openRawResource(resId); + OutputStream target = new FileOutputStream(file)) { + FileUtils.copy(source, target); + } + return file; + } - // Force sync to try updating other views - executeShellCommand("sync"); - } - } else { - final File dir = file.getParentFile(); - dir.mkdirs(); - if (!dir.exists()) { - throw new FileNotFoundException("Failed to create parent for " + file); - } - try (InputStream source = context.getResources().openRawResource(resId); - OutputStream target = new FileOutputStream(file)) { - FileUtils.copy(source, target); - } + static File stageFileWithRawText(String rawText, File file) throws IOException { + final File dir = file.getParentFile(); + dir.mkdirs(); + if (!dir.exists()) { + throw new FileNotFoundException("Failed to create parent for " + file); + } + try (InputStream source = new ByteArrayInputStream( + rawText.getBytes(StandardCharsets.UTF_8)); + OutputStream target = new FileOutputStream(file)) { + FileUtils.copy(source, target); } return file; } diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp index 4fd9b5152d3..e249fe342da 100644 --- a/tests/tests/permission3/Android.bp +++ b/tests/tests/permission3/Android.bp @@ -47,6 +47,7 @@ android_test { ":CtsUsePermissionApp30", ":CtsUsePermissionApp30WithBackground", ":CtsUsePermissionApp30WithBluetooth", + ":CtsUsePermissionApp31", ":CtsUsePermissionAppLatest", ":CtsUsePermissionAppLatestNone", ":CtsUsePermissionAppWithOverlay", diff --git a/tests/tests/permission3/AndroidTest.xml b/tests/tests/permission3/AndroidTest.xml index 1536d580229..2342b6a4530 100644 --- a/tests/tests/permission3/AndroidTest.xml +++ b/tests/tests/permission3/AndroidTest.xml @@ -54,6 +54,7 @@ <option name="push" value="CtsUsePermissionApp30.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30.apk" /> <option name="push" value="CtsUsePermissionApp30WithBackground.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBackground.apk" /> <option name="push" value="CtsUsePermissionApp30WithBluetooth.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBluetooth.apk" /> + <option name="push" value="CtsUsePermissionApp31.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp31.apk" /> <option name="push" value="CtsUsePermissionAppLatest.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatest.apk" /> <option name="push" value="CtsUsePermissionAppLatestNone.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatestNone.apk" /> <option name="push" value="CtsUsePermissionAppWithOverlay.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppWithOverlay.apk" /> diff --git a/tests/tests/permission3/UsePermissionApp31/Android.bp b/tests/tests/permission3/UsePermissionApp31/Android.bp new file mode 100644 index 00000000000..48a2d4fa901 --- /dev/null +++ b/tests/tests/permission3/UsePermissionApp31/Android.bp @@ -0,0 +1,32 @@ +// +// Copyright (C) 2021 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test_helper_app { + name: "CtsUsePermissionApp31", + srcs: [ + ":CtsUsePermissionAppSrc", + ], + static_libs: [ + "kotlin-stdlib", + ], + certificate: ":cts-testkey2", + target_sdk_version: "31", + min_sdk_version: "31", +} diff --git a/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml new file mode 100644 index 00000000000..f6d9b29221b --- /dev/null +++ b/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 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. + --> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="android.permission3.cts.usepermission"> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.RECORD_AUDIO" /> + <uses-permission android:name="android.permission.CAMERA" /> + <application> + <activity android:name=".CheckCalendarAccessActivity" android:exported="true" /> + <activity android:name=".FinishOnCreateActivity" android:exported="true" /> + <activity android:name=".RequestPermissionsActivity" android:exported="true" /> + </application> +</manifest> diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt index ece58d93bee..6c69a6522ac 100644 --- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt +++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt @@ -52,6 +52,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsUsePermissionApp28.apk" const val APP_APK_PATH_29 = "$APK_DIRECTORY/CtsUsePermissionApp29.apk" const val APP_APK_PATH_30 = "$APK_DIRECTORY/CtsUsePermissionApp30.apk" + const val APP_APK_PATH_31 = "$APK_DIRECTORY/CtsUsePermissionApp31.apk" + const val APP_APK_PATH_30_WITH_BACKGROUND = "$APK_DIRECTORY/CtsUsePermissionApp30WithBackground.apk" const val APP_APK_PATH_30_WITH_BLUETOOTH = diff --git a/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt new file mode 100644 index 00000000000..37e9d41fe5c --- /dev/null +++ b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2021 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.permission3.cts + +import android.content.Intent +import android.hardware.SensorPrivacyManager +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE +import android.location.LocationManager +import android.os.Build +import android.provider.DeviceConfig +import android.provider.Settings +import android.support.test.uiautomator.By +import androidx.test.filters.SdkSuppress +import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity +import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Test + +/** + * Banner card display tests on sensors being blocked + */ +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) +class SensorBlockedBannerTest : BaseUsePermissionTest() { + companion object { + const val LOCATION = -1 + const val WARNING_BANNER_ENABLED = "warning_banner_enabled" + } + + val sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!! + val locationManager = context.getSystemService(LocationManager::class.java)!! + private val originalEnabledValue = callWithShellPermissionIdentity { + DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY, + WARNING_BANNER_ENABLED, false.toString()) + } + + private val permToLabel = mapOf(CAMERA to "privdash_label_camera", + MICROPHONE to "privdash_label_microphone", + LOCATION to "privdash_label_location") + + private val permToTitle = mapOf(CAMERA to "blocked_camera_title", + MICROPHONE to "blocked_microphone_title", + LOCATION to "blocked_location_title") + + @Before + fun setup() { + Assume.assumeFalse(isTv) + // TODO(b/203784852) Auto will eventually support the blocked sensor banner, but there won't + // be support in T or below + Assume.assumeFalse(isAutomotive) + installPackage(APP_APK_PATH_31) + runWithShellPermissionIdentity { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, + WARNING_BANNER_ENABLED, true.toString(), false) + } + } + + @After + fun restoreWarningBannerState() { + runWithShellPermissionIdentity { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, + WARNING_BANNER_ENABLED, originalEnabledValue, false) + } + } + + private fun navigateAndTest(sensor: Int) { + val permLabel = permToLabel.getOrDefault(sensor, "Break") + val intent = Intent(Settings.ACTION_PRIVACY_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + click(By.text(getPermissionControllerString("app_permission_manager"))) + click(By.text(getPermissionControllerString(permLabel))) + val bannerTitle = permToTitle.getOrDefault(sensor, "Break") + waitFindObject(By.text(getPermissionControllerString(bannerTitle))) + pressBack() + pressBack() + pressBack() + } + + private fun runSensorTest(sensor: Int) { + val blocked = isSensorPrivacyEnabled(sensor) + if (!blocked) { + setSensor(sensor, true) + } + navigateAndTest(sensor) + if (!blocked) { + setSensor(sensor, false) + } + } + + @Test + fun testCameraCardDisplayed() { + Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(CAMERA)) + runSensorTest(CAMERA) + } + + @Test + fun testMicCardDisplayed() { + Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(MICROPHONE)) + runSensorTest(MICROPHONE) + } + + @Test + fun testLocationCardDisplayed() { + runSensorTest(LOCATION) + } + + private fun setSensor(sensor: Int, enable: Boolean) { + if (sensor == LOCATION) { + runWithShellPermissionIdentity { + locationManager.setLocationEnabledForUser(!enable, + android.os.Process.myUserHandle()) + if (enable) { + try { + waitFindObjectOrNull(By.text("CLOSE"))?.click() + } catch (e: Exception) { + // Do nothing, warning didn't show up so test can proceed + } + } + } + } else { + runWithShellPermissionIdentity { + sensorPrivacyManager.setSensorPrivacy(SensorPrivacyManager.Sources.OTHER, + sensor, enable) + } + } + } + + private fun isSensorPrivacyEnabled(sensor: Int): Boolean { + return if (sensor == LOCATION) { + callWithShellPermissionIdentity { + !locationManager.isLocationEnabled() + } + } else { + callWithShellPermissionIdentity { + sensorPrivacyManager.isSensorPrivacyEnabled(sensor) + } + } + } +} |