summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hostsidetests/edi/src/android/edi/cts/ClasspathDeviceInfo.java16
-rw-r--r--hostsidetests/jdwptunnel/src/android/jdwptunnel/cts/JdwpTunnelTest.java12
-rw-r--r--hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java73
-rw-r--r--hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java5
-rw-r--r--hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java20
-rw-r--r--hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java19
-rw-r--r--hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java17
-rw-r--r--hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java62
-rw-r--r--hostsidetests/securitybulletin/res/cve_2021_0925bin0 -> 12 bytes
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/Android.bp37
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/poc.cpp43
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/Android.bp1
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/poc.cpp49
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/Android.bp1
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/poc.cpp61
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/Android.bp31
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/poc.cpp95
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/Android.bp5
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/poc.cpp37
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/Android.bp48
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/poc.cpp102
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/stubs.cpp144
-rw-r--r--hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/t4t.cpp207
-rw-r--r--hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java2
-rw-r--r--hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9549.java44
-rw-r--r--hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2014.java2
-rw-r--r--hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2027.java44
-rw-r--r--hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0925.java43
-rw-r--r--hostsidetests/securitybulletin/test-apps/CVE-2021-0921/src/android/security/cts/CVE_2021_0921/DeviceTest.java8
-rw-r--r--tests/PhotoPicker/Android.bp35
-rw-r--r--tests/PhotoPicker/AndroidManifest.xml29
-rw-r--r--tests/PhotoPicker/AndroidTest.xml41
-rw-r--r--tests/PhotoPicker/OWNERS3
-rw-r--r--tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpgbin0 -> 107684 bytes
-rw-r--r--tests/PhotoPicker/res/raw/testvideo_meta.mp4bin0 -> 20716 bytes
-rw-r--r--tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java84
-rw-r--r--tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java57
-rw-r--r--tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java147
-rw-r--r--tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java237
-rw-r--r--tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java161
-rw-r--r--tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java116
-rw-r--r--tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java90
42 files changed, 2183 insertions, 45 deletions
diff --git a/hostsidetests/edi/src/android/edi/cts/ClasspathDeviceInfo.java b/hostsidetests/edi/src/android/edi/cts/ClasspathDeviceInfo.java
index 717e2e2bc0f..463934f1550 100644
--- a/hostsidetests/edi/src/android/edi/cts/ClasspathDeviceInfo.java
+++ b/hostsidetests/edi/src/android/edi/cts/ClasspathDeviceInfo.java
@@ -18,6 +18,8 @@ package android.edi.cts;
import static android.compat.testing.Classpaths.ClasspathType.BOOTCLASSPATH;
import static android.compat.testing.Classpaths.ClasspathType.SYSTEMSERVERCLASSPATH;
+import static org.junit.Assume.assumeTrue;
+
import android.compat.testing.Classpaths;
import android.compat.testing.Classpaths.ClasspathType;
import android.compat.testing.SharedLibraryInfo;
@@ -33,6 +35,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.jf.dexlib2.iface.ClassDef;
+import org.junit.Assume;
+import org.junit.Before;
import java.io.File;
import java.io.IOException;
@@ -44,15 +48,17 @@ import java.util.stream.IntStream;
*/
public class ClasspathDeviceInfo extends DeviceInfo {
- private ITestDevice mDevice;
- private DeviceSdkLevel mDeviceSdkLevel;
+ private final ITestDevice mDevice = getDevice();
+ private final DeviceSdkLevel mDeviceSdkLevel = new DeviceSdkLevel(mDevice);
private final Object mStoreLock = new Object();
+ @Before
+ public void before() throws DeviceNotAvailableException {
+ assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR());
+ }
+
@Override
protected void collectDeviceInfo(HostInfoStore store) throws Exception {
- mDevice = getDevice();
- mDeviceSdkLevel = new DeviceSdkLevel(mDevice);
-
store.startArray("jars");
collectClasspathsJars(store);
collectSharedLibraries(store);
diff --git a/hostsidetests/jdwptunnel/src/android/jdwptunnel/cts/JdwpTunnelTest.java b/hostsidetests/jdwptunnel/src/android/jdwptunnel/cts/JdwpTunnelTest.java
index 5fbc913b073..879160207f2 100644
--- a/hostsidetests/jdwptunnel/src/android/jdwptunnel/cts/JdwpTunnelTest.java
+++ b/hostsidetests/jdwptunnel/src/android/jdwptunnel/cts/JdwpTunnelTest.java
@@ -26,6 +26,7 @@ import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.AbiUtils;
import com.sun.jdi.Bootstrap;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.VirtualMachine;
@@ -41,6 +42,7 @@ import java.io.*;
import java.net.Socket;
import java.time.Instant;
import java.util.Map;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -269,6 +271,11 @@ public class JdwpTunnelTest extends BaseHostJUnit4Test {
}
}
+ private String getDeviceBaseArch() throws Exception {
+ String abi = mDevice.executeShellCommand("getprop ro.product.cpu.abi").replace("\n", "");
+ return AbiUtils.getBaseArchForAbi(abi);
+ }
+
/**
* Tests that we don't get any DDMS messages before the handshake.
*
@@ -277,6 +284,11 @@ public class JdwpTunnelTest extends BaseHostJUnit4Test {
*/
@Test
public void testDdmsWaitsForHandshake() throws DeviceNotAvailableException, Exception {
+ // Skip this test if not running on the device's native abi.
+ String testingArch = AbiUtils.getBaseArchForAbi(getAbi().getName());
+ String deviceArch = getDeviceBaseArch();
+ Assume.assumeTrue(testingArch.equals(deviceArch));
+
String port =
startupForwarding(DDMS_TEST_APP_PACKAGE_NAME, DDMS_TEST_APP_ACTIVITY_CLASS_NAME, false);
Socket sock = new Socket("localhost", Integer.decode(port).intValue());
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
index 605f85cacac..3c860132c50 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -85,8 +85,10 @@ import static android.scopedstorage.cts.lib.TestUtils.readExifMetadataFromTestAp
import static android.scopedstorage.cts.lib.TestUtils.revokePermission;
import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid;
import static android.scopedstorage.cts.lib.TestUtils.setAttrAs;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
import static android.scopedstorage.cts.lib.TestUtils.updateDisplayNameWithMediaProvider;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
@@ -2057,7 +2059,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest {
// file.
assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
- trashFile(imageFileUri);
+ trashFileAndAssert(imageFileUri);
// Check that only owner package, file manager and system gallery can list trashed image
// file.
assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
@@ -2066,7 +2068,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest {
// Check that only owner package, file manager can list pending non media file.
assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
- trashFile(pdfFileUri);
+ trashFileAndAssert(pdfFileUri);
// Check that only owner package, file manager can list trashed non media file.
assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
} finally {
@@ -2198,6 +2200,65 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest {
}
@Test
+ public void testSystemGalleryCanTrashOtherAndroidMediaFiles() throws Exception {
+ final File otherVideoFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+ try {
+ allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+ assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+ final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+ assertNotNull(otherVideoUri);
+
+ trashFileAndAssert(otherVideoUri);
+ untrashFileAndAssert(otherVideoUri);
+ } finally {
+ otherVideoFile.delete();
+ denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ @Test
+ public void testSystemGalleryCanUpdateOtherAndroidMediaFiles() throws Exception {
+ final File otherImageFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), IMAGE_FILE_NAME));
+ final File updatedImageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME);
+ try {
+ allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+ assertThat(createFileAs(APP_B_NO_PERMS, otherImageFile.getAbsolutePath())).isTrue();
+
+ final Uri otherImageUri = MediaStore.scanFile(getContentResolver(), otherImageFile);
+ assertNotNull(otherImageUri);
+
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
+ // Test that we can move the file to "DCIM/"
+ assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+ + " with values " + values)
+ .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+ .isEqualTo(1);
+ assertThat(updatedImageFileInDcim.exists()).isTrue();
+ assertThat(otherImageFile.exists()).isFalse();
+
+ values.clear();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH,
+ "Android/media/" + APP_B_NO_PERMS.getPackageName());
+ // Test that we can move the file back to other app's owned path
+ assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+ + " with values " + values)
+ .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+ .isEqualTo(1);
+ assertThat(otherImageFile.exists()).isTrue();
+ } finally {
+ otherImageFile.delete();
+ updatedImageFileInDcim.delete();
+ denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+ }
+ }
+
+ @Test
public void testQueryOtherAppsFiles() throws Exception {
final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
@@ -2999,16 +3060,10 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest {
final Uri trashedFileUri = MediaStore.scanFile(cr, trashedFile);
assertNotNull(trashedFileUri);
- trashFile(trashedFileUri);
+ trashFileAndAssert(trashedFileUri);
return trashedFileUri;
}
- private void trashFile(Uri uri) throws Exception {
- final ContentValues values = new ContentValues();
- values.put(MediaStore.MediaColumns.IS_TRASHED, 1);
- assertEquals(1, getContentResolver().update(uri, values, Bundle.EMPTY));
- }
-
/**
* Gets file path corresponding to the db row pointed by {@code uri}. If {@code uri} points to
* multiple db rows, file path is extracted from the first db row of the database query result.
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
index 3b8164625f7..e4d3541b044 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
@@ -147,6 +147,11 @@ public class LegacyStorageHostTest extends BaseHostTestCase {
}
@Test
+ public void testCanTrashOtherAndroidMediaFiles_hasRW() throws Exception {
+ runDeviceTest("testCanTrashOtherAndroidMediaFiles_hasRW");
+ }
+
+ @Test
public void testCantRename_hasR() throws Exception {
revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
runDeviceTest("testCantRename_hasR");
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index f1236b6e3eb..d31bc33dfa9 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -179,6 +179,26 @@ public class ScopedStorageHostTest extends BaseHostTestCase {
}
@Test
+ public void testFileManagerCanTrashOtherAndroidMediaFiles() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testFileManagerCanTrashOtherAndroidMediaFiles");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
+ public void testFileManagerCanUpdateOtherAndroidMediaFiles() throws Exception {
+ allowAppOps("android:manage_external_storage");
+ try {
+ runDeviceTest("testFileManagerCanUpdateOtherAndroidMediaFiles");
+ } finally {
+ denyAppOps("android:manage_external_storage");
+ }
+ }
+
+ @Test
public void testOpenOtherPendingFilesFromFuse() throws Exception {
grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
try {
diff --git a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
index 4754d74ec5d..fd83a2ef74f 100644
--- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
+++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
@@ -49,6 +49,8 @@ import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageStat
import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
import static android.scopedstorage.cts.lib.TestUtils.resetDefaultExternalStorageVolume;
import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
import static android.scopedstorage.cts.lib.TestUtils.updateFile;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
@@ -431,6 +433,23 @@ public class LegacyStorageTest {
}
}
+ @Test
+ public void testCanTrashOtherAndroidMediaFiles_hasRW() throws Exception {
+ final File otherVideoFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+ try {
+ assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+ final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+ assertNotNull(otherVideoUri);
+
+ trashFileAndAssert(otherVideoUri);
+ untrashFileAndAssert(otherVideoUri);
+ } finally {
+ otherVideoFile.delete();
+ }
+ }
+
/**
* Test that legacy app with only READ_EXTERNAL_STORAGE can only rename files in app external
* directories.
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
index 64e500a0788..30683328b35 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
@@ -1690,4 +1690,21 @@ public class TestUtils {
ActivityManager.class).getRunningAppProcesses().stream().filter(
p -> packageName.equals(p.processName)).findFirst();
}
+
+ public static void trashFileAndAssert(Uri uri) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.IS_TRASHED, 1);
+ assertWithMessage("Result of ContentResolver#update for " + uri + " with values to trash "
+ + "file " + values)
+ .that(getContentResolver().update(uri, values, Bundle.EMPTY)).isEqualTo(1);
+ }
+
+ public static void untrashFileAndAssert(Uri uri) {
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.IS_TRASHED, 0);
+ assertWithMessage("Result of ContentResolver#update for " + uri + " with values to untrash "
+ + "file " + values)
+ .that(getContentResolver().update(uri, values, Bundle.EMPTY)).isEqualTo(1);
+ }
+
}
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index c915235c607..814f1a24654 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -52,6 +52,8 @@ import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageStat
import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed;
import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaData_denied;
@@ -66,6 +68,7 @@ import static android.system.OsConstants.W_OK;
import static androidx.test.InstrumentationRegistry.getContext;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -76,8 +79,10 @@ import static org.junit.Assume.assumeTrue;
import android.Manifest;
import android.app.WallpaperManager;
+import android.content.ContentValues;
import android.net.Uri;
import android.os.Build;
+import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
@@ -122,6 +127,7 @@ public class ScopedStorageTest {
static final String AUDIO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp3";
static final String IMAGE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".jpg";
+ static final String VIDEO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp4";
static final String NONMEDIA_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".pdf";
// The following apps are installed before the tests are run via a target_preparer.
@@ -534,6 +540,62 @@ public class ScopedStorageTest {
}
@Test
+ public void testFileManagerCanTrashOtherAndroidMediaFiles() throws Exception {
+ pollForManageExternalStorageAllowed();
+
+ final File otherVideoFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+ try {
+ assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+ final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+ assertNotNull(otherVideoUri);
+
+ trashFileAndAssert(otherVideoUri);
+ untrashFileAndAssert(otherVideoUri);
+ } finally {
+ otherVideoFile.delete();
+ }
+ }
+
+ @Test
+ public void testFileManagerCanUpdateOtherAndroidMediaFiles() throws Exception {
+ pollForManageExternalStorageAllowed();
+
+ final File otherImageFile = new File(getAndroidMediaDir(),
+ String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), IMAGE_FILE_NAME));
+ final File updatedImageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME);
+ try {
+ assertThat(createFileAs(APP_B_NO_PERMS, otherImageFile.getAbsolutePath())).isTrue();
+
+ final Uri otherImageUri = MediaStore.scanFile(getContentResolver(), otherImageFile);
+ assertNotNull(otherImageUri);
+
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
+ // Test that we can move the file to "DCIM/"
+ assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+ + " with values " + values)
+ .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+ .isEqualTo(1);
+ assertThat(updatedImageFileInDcim.exists()).isTrue();
+ assertThat(otherImageFile.exists()).isFalse();
+
+ values.clear();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH,
+ "Android/media/" + APP_B_NO_PERMS.getPackageName());
+ // Test that we can move the file back to other app's owned path
+ assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+ + " with values " + values)
+ .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+ .isEqualTo(1);
+ } finally {
+ otherImageFile.delete();
+ updatedImageFileInDcim.delete();
+ }
+ }
+
+ @Test
public void testAndroidMedia() throws Exception {
// Check that the app does not have legacy external storage access
if (isAtLeastS()) {
diff --git a/hostsidetests/securitybulletin/res/cve_2021_0925 b/hostsidetests/securitybulletin/res/cve_2021_0925
new file mode 100644
index 00000000000..be9690ca0d0
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2021_0925
Binary files differ
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/Android.bp
new file mode 100644
index 00000000000..5b9e6ddba7c
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/Android.bp
@@ -0,0 +1,37 @@
+/*
+ * 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"],
+}
+
+cc_test {
+ name : "CVE-2018-9549",
+ defaults : ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs : [
+ "poc.cpp",
+ ],
+ shared_libs : [
+ "libbluetooth",
+ ],
+ include_dirs : [
+ "external/aac/libSBRdec/src",
+ "external/aac/libSBRdec/include",
+ "external/aac/libFDK/include",
+ "external/aac/libSYS/include",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/poc.cpp
new file mode 100644
index 00000000000..11165d29065
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/poc.cpp
@@ -0,0 +1,43 @@
+/**
+ * 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.
+ */
+
+#include <stdlib.h>
+#include "lpp_tran.h"
+
+constexpr uint8_t kDegreeAliasMaxSize = 64;
+
+int main() {
+ FIXP_DBL qmfBufferRealVal = {};
+ FIXP_DBL *qmfBufferReal = &qmfBufferRealVal;
+ FIXP_DBL qmfBufferImagVal = {};
+ FIXP_DBL *qmfBufferImag = &qmfBufferImagVal;
+ QMF_SCALE_FACTOR sbrScaleFactor = {};
+ FIXP_DBL degreeAlias[kDegreeAliasMaxSize] = {};
+
+ SBR_LPP_TRANS hLppTransVal = {};
+ HANDLE_SBR_LPP_TRANS hLppTrans = &hLppTransVal;
+ TRANSPOSER_SETTINGS settings = {};
+ for (int32_t i = 0; i < MAX_NUM_NOISE_VALUES; ++i) {
+ settings.bwBorders[i] = 64;
+ }
+ settings.nCols = 1;
+ hLppTrans->pSettings = &settings;
+
+ lppTransposer(hLppTrans, &sbrScaleFactor, &qmfBufferReal, degreeAlias, &qmfBufferImag, 1, 0, 0,
+ 0, 0, 0, 0, nullptr, nullptr);
+
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/Android.bp
index 61166aa2d39..de0a9aa6aed 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/Android.bp
@@ -38,5 +38,6 @@ cc_test {
],
cflags: [
"-DCHECK_OVERFLOW",
+ "-DENABLE_SELECTIVE_OVERLOADING",
],
}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/poc.cpp
index c8734bb370b..5205d0588b8 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/poc.cpp
@@ -17,6 +17,29 @@
#include <stdlib.h>
#include <nfc_int.h>
#include <rw_int.h>
+#include <unistd.h>
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+char enable_selective_overload = ENABLE_NONE;
+char *vulnPtr = nullptr;
+
+bool testInProgress = false;
+struct sigaction new_action, old_action;
+void sigsegv_handler(int signum, siginfo_t *info, void* context) {
+ if (testInProgress && info->si_signo == SIGSEGV) {
+ size_t pageSize = getpagesize();
+ if (pageSize) {
+ char *vulnPtrGuardPage = (char *) ((size_t) vulnPtr & PAGE_MASK) + pageSize;
+ char *faultPage = (char *) ((size_t) info->si_addr & PAGE_MASK);
+ if (faultPage == vulnPtrGuardPage) {
+ (*old_action.sa_sigaction)(signum, info, context);
+ return;
+ }
+ }
+ }
+ _exit(EXIT_FAILURE);
+}
extern tRW_CB rw_cb;
extern tNFC_CB nfc_cb;
@@ -26,7 +49,7 @@ tNFC_STATUS rw_t3t_select(uint8_t peer_nfcid2[NCI_RF_F_UID_LEN],
uint8_t mrti_check, uint8_t mrti_update);
void *allocate_memory(size_t size) {
- void *ptr = malloc(size);
+ void *ptr = memalign(16, size);
memset(ptr, 0x0, size);
return ptr;
}
@@ -67,6 +90,10 @@ void GKI_stop_timer(uint8_t) {
}
int main() {
+ sigemptyset(&new_action.sa_mask);
+ new_action.sa_flags = SA_SIGINFO;
+ new_action.sa_sigaction = sigsegv_handler;
+ sigaction(SIGSEGV, &new_action, &old_action);
tRW_T3T_CB* p_t3t = &rw_cb.tcb.t3t;
GKI_init();
@@ -75,20 +102,20 @@ int main() {
uint8_t peer_nfcid2[NCI_RF_F_UID_LEN];
uint8_t mrti_check = 1, mrti_update = 1;
- if (rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) != NFC_STATUS_OK) {
- return EXIT_FAILURE;
- }
+
+ enable_selective_overload = ENABLE_MEMALIGN_CHECK;
+ FAIL_CHECK((rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) == NFC_STATUS_OK));
p_data = (tNFC_CONN *) allocate_memory(sizeof(tNFC_CONN));
- if (!p_data) {
- return EXIT_FAILURE;
- }
+ FAIL_CHECK(p_data);
- p_data->data.p_data = (NFC_HDR*)allocate_memory(3 * sizeof(NFC_HDR));
- if(!p_data->data.p_data) {
+ p_data->data.p_data = (NFC_HDR *) allocate_memory(sizeof(NFC_HDR) * 3);
+ enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK;
+ if (!(p_data->data.p_data)) {
free(p_data);
- return EXIT_FAILURE;
+ FAIL_CHECK(p_data->data.p_data);
}
+ vulnPtr = (char *)p_data->data.p_data;
p_data->status = NFC_STATUS_OK;
p_t3t->rw_state = RW_T3T_STATE_COMMAND_PENDING;
@@ -105,6 +132,8 @@ int main() {
tNFC_CONN_EVT event = NFC_DATA_CEVT;
memcpy(p_t3t->peer_nfcid2, &p_t3t_rsp[T3T_MSG_RSP_OFFSET_IDM],
NCI_NFCID2_LEN);
+ testInProgress = true;
p_cb->p_cback(0, event, p_data);
+ testInProgress = false;
return EXIT_SUCCESS;
}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/Android.bp
index 2187f920734..2c21381b503 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/Android.bp
@@ -38,5 +38,6 @@ cc_test {
],
cflags: [
"-DCHECK_OVERFLOW",
+ "-DENABLE_SELECTIVE_OVERLOADING",
],
}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/poc.cpp
index f2150857d61..b9252c5fd54 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/poc.cpp
@@ -20,6 +20,29 @@
#include <nfc_api.h>
#include <tags_defs.h>
#include <rw_int.h>
+#include <unistd.h>
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+char enable_selective_overload = ENABLE_NONE;
+char *vulnPtr = nullptr;
+
+bool testInProgress = false;
+struct sigaction new_action, old_action;
+void sigsegv_handler(int signum, siginfo_t *info, void* context) {
+ if (testInProgress && info->si_signo == SIGSEGV) {
+ size_t pageSize = getpagesize();
+ if (pageSize) {
+ char *vulnPtrGuardPage = (char *) ((size_t) vulnPtr & PAGE_MASK) + pageSize;
+ char *faultPage = (char *) ((size_t) info->si_addr & PAGE_MASK);
+ if (faultPage == vulnPtrGuardPage) {
+ (*old_action.sa_sigaction)(signum, info, context);
+ return;
+ }
+ }
+ }
+ _exit(EXIT_FAILURE);
+}
#define T3T_MSG_FELICALITE_MC_OFFSET 0x01
@@ -31,7 +54,7 @@ tNFC_STATUS rw_t3t_select(uint8_t peer_nfcid2[NCI_RF_F_UID_LEN],
uint8_t mrti_check, uint8_t mrti_update);
void *allocate_memory(size_t size) {
- void *ptr = malloc(size);
+ void *ptr = memalign(16, size);
memset(ptr, 0x0, size);
return ptr;
}
@@ -104,19 +127,19 @@ int trigger_OOB_via_rw_t3t_act_handle_fmt_rsp(){
uint8_t peer_nfcid2[NCI_RF_F_UID_LEN];
uint8_t mrti_check = 1, mrti_update = 1;
- if (rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) != NFC_STATUS_OK) {
- return EXIT_FAILURE;
- }
+ enable_selective_overload = ENABLE_MEMALIGN_CHECK;
+ FAIL_CHECK(rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) == NFC_STATUS_OK);
p_data = (tNFC_CONN *) allocate_memory(sizeof(tNFC_CONN));
- if (!p_data) {
- return EXIT_FAILURE;
- }
+ FAIL_CHECK(p_data);
+
p_data->data.p_data = (NFC_HDR *) allocate_memory(sizeof(NFC_HDR) * 4);
+ enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK;
if (!(p_data->data.p_data)) {
free(p_data);
- return EXIT_FAILURE;
+ FAIL_CHECK(p_data->data.p_data);
}
+ vulnPtr = (char *)p_data->data.p_data;
p_data->status = NFC_STATUS_OK;
p_t3t->cur_cmd = RW_T3T_CMD_FORMAT;
@@ -137,7 +160,9 @@ int trigger_OOB_via_rw_t3t_act_handle_fmt_rsp(){
tNFC_CONN_EVT event = NFC_DATA_CEVT;
memcpy(p_t3t->peer_nfcid2, &p_t3t_rsp[T3T_MSG_RSP_OFFSET_IDM],
NCI_NFCID2_LEN);
+ testInProgress = true;
p_cb->p_cback(0, event, p_data);
+ testInProgress = false;
free(p_data->data.p_data);
free(p_data);
return EXIT_SUCCESS;
@@ -152,19 +177,19 @@ int trigger_OOB_via_rw_t3t_act_handle_sro_rsp(){
uint8_t peer_nfcid2[NCI_RF_F_UID_LEN];
uint8_t mrti_check = 1, mrti_update = 1;
- if (rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) != NFC_STATUS_OK) {
- return EXIT_FAILURE;
- }
+ enable_selective_overload = ENABLE_MEMALIGN_CHECK;
+ FAIL_CHECK(rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) == NFC_STATUS_OK);
tNFC_CONN *p_data = (tNFC_CONN *) allocate_memory(sizeof(tNFC_CONN));
- if (!p_data) {
- return EXIT_FAILURE;
- }
+ FAIL_CHECK(p_data);
+
p_data->data.p_data = (NFC_HDR *) allocate_memory(sizeof(NFC_HDR) * 4);
+ enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK;
if (!(p_data->data.p_data)) {
free(p_data);
- return EXIT_FAILURE;
+ FAIL_CHECK(p_data->data.p_data);
}
+ vulnPtr = (char *)p_data->data.p_data;
p_data->status = NFC_STATUS_OK;
p_t3t->cur_cmd = RW_T3T_CMD_SET_READ_ONLY_HARD;
@@ -184,13 +209,19 @@ int trigger_OOB_via_rw_t3t_act_handle_sro_rsp(){
tNFC_CONN_CB* p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
tNFC_CONN_EVT event = NFC_DATA_CEVT;
+ testInProgress = true;
p_cb->p_cback(0, event, p_data);
+ testInProgress = false;
free(p_data->data.p_data);
free(p_data);
return EXIT_SUCCESS;
}
int main() {
+ sigemptyset(&new_action.sa_mask);
+ new_action.sa_flags = SA_SIGINFO;
+ new_action.sa_sigaction = sigsegv_handler;
+ sigaction(SIGSEGV, &new_action, &old_action);
int ret = trigger_OOB_via_rw_t3t_act_handle_fmt_rsp();
ret |= trigger_OOB_via_rw_t3t_act_handle_sro_rsp();
return ret;
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/Android.bp
new file mode 100644
index 00000000000..a080e0870c9
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/Android.bp
@@ -0,0 +1,31 @@
+/*
+ * 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"],
+}
+
+cc_test {
+ name: "CVE-2019-2027",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ ],
+ shared_libs: [
+ "libvorbisidec",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/poc.cpp
new file mode 100644
index 00000000000..b1426ee10a6
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/poc.cpp
@@ -0,0 +1,95 @@
+/**
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "../includes/common.h"
+
+#define REF_COUNT 1
+
+extern "C" {
+#include <Tremolo/codebook.h>
+}
+
+bool testInProgress = false;
+struct sigaction new_action, old_action;
+void sigabrt_handler(int signum, siginfo_t *info, void* context) {
+ if (testInProgress && info->si_signo == SIGABRT) {
+ (*old_action.sa_sigaction)(signum, info, context);
+ return;
+ }
+ _exit(EXIT_FAILURE);
+}
+
+unsigned char data[] = {/* 24 bits to make sure the alignment is correct */
+ 0x42, 0x43, 0x56,
+ /* 16 bits for codebook.dim */
+ 0x40, 0x00,
+ /* 24 bits for codebook.entries */
+ 0x10, 0x00, 0x00,
+ /* 1 bit for ordering which is unset for unordered */
+ /* 1 bit set for specifying unused entries */
+ /* 1 bit for valid length */
+ /* 5 bits for length of entry */
+ 0x06,
+ /* 1 bit for valid length */
+ /* 5 bits for length of entry */
+ /* 2 bits for specifying invalid length for next 2 entries */
+ 0x01,
+ /* 8 bits for specifying invalid length for next 8 entries */
+ 0x00,
+ /* 4 bits for specifying invalid length for next 4 entries */
+ /* 4 bits for specifying the map type 1 */
+ 0x10,
+ /* 32 bits for codebook.q_min */
+ 0x00, 0x00, 0x00, 0x00,
+ /* 32 bits for codebook.q_del */
+ 0x00, 0x00, 0x00, 0x00,
+ /* 4 bits for codebook.q_bits */
+ /* 1 bit for codebook.q_seq */
+ /* 4 bits for quantized values of codebook.q_val for quantvals = 2 */
+ /* 7 bits remaining unused */
+ 0x01, 0x00};
+
+int main() {
+ sigemptyset(&new_action.sa_mask);
+ new_action.sa_flags = SA_SIGINFO;
+ new_action.sa_sigaction = sigabrt_handler;
+ sigaction(SIGABRT, &new_action, &old_action);
+
+ ogg_buffer buf;
+ ogg_reference ref;
+ oggpack_buffer bits;
+ codebook book = {};
+
+ memset(&buf, 0, sizeof(ogg_buffer));
+ memset(&ref, 0, sizeof(ogg_reference));
+ memset(&bits, 0, sizeof(oggpack_buffer));
+
+ buf.data = (uint8_t *)data;
+ buf.size = sizeof(data);
+ buf.refcount = REF_COUNT;
+
+ ref.buffer = &buf;
+ ref.length = sizeof(data);
+ oggpack_readinit(&bits, &ref);
+
+ testInProgress = true;
+ FAIL_CHECK(vorbis_book_unpack(&bits, &book) == 0);
+ testInProgress = false;
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/Android.bp
index 470f2561c51..cdcf942d497 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/Android.bp
@@ -15,6 +15,10 @@
*
*/
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_test {
name: "CVE-2021-0596",
defaults: ["cts_hostsidetests_securitybulletin_defaults"],
@@ -38,5 +42,6 @@ cc_test {
],
cflags: [
"-DCHECK_UNDERFLOW",
+ "-DENABLE_SELECTIVE_OVERLOADING",
],
}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/poc.cpp
index 9b250044e38..3b1a58014a3 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/poc.cpp
@@ -15,21 +15,50 @@
*
*/
+#include <unistd.h>
#include "phNxpExtns_MifareStd.h"
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+char enable_selective_overload = ENABLE_NONE;
+char *vulnPtr = nullptr;
+bool testInProgress = false;
+struct sigaction new_action, old_action;
+void sigsegv_handler(int signum, siginfo_t *info, void* context) {
+ if (testInProgress && info->si_signo == SIGSEGV) {
+ size_t pageSize = getpagesize();
+ if (pageSize) {
+ char *vulnPtrGuardPage = (char *) ((size_t) vulnPtr & PAGE_MASK) - pageSize;
+ char *faultPage = (char *) ((size_t) info->si_addr & PAGE_MASK);
+ if (faultPage == vulnPtrGuardPage) {
+ (*old_action.sa_sigaction)(signum, info, context);
+ return;
+ }
+ }
+ }
+ _exit(EXIT_FAILURE);
+}
uint8_t NFC_GetNCIVersion() {
return NCI_VERSION_2_0;
}
int main() {
- uint8_t *buffer = (uint8_t*) malloc(16 * sizeof(uint8_t));
- if (buffer == nullptr) {
- return EXIT_FAILURE;
- }
+ sigemptyset(&new_action.sa_mask);
+ new_action.sa_flags = SA_SIGINFO;
+ new_action.sa_sigaction = sigsegv_handler;
+ sigaction(SIGSEGV, &new_action, &old_action);
+ enable_selective_overload = ENABLE_MEMALIGN_CHECK;
+ uint8_t *buffer = (uint8_t*) memalign(16, 16 * sizeof(uint8_t));
+ enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK;
+ FAIL_CHECK(buffer);
+
+ vulnPtr = (char *) buffer;
uint8_t bufferSize = 1;
buffer[0] = 0x10;
phNxpExtns_MfcModuleInit();
+ testInProgress = true;
Mfc_RecvPacket(buffer, bufferSize);
+ testInProgress = false;
free(buffer);
return EXIT_SUCCESS;
}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/Android.bp
new file mode 100644
index 00000000000..eea87e76748
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/Android.bp
@@ -0,0 +1,48 @@
+/*
+ * 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"],
+}
+
+cc_test {
+ name: "CVE-2021-0925",
+ defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+ srcs: [
+ "poc.cpp",
+ "stubs.cpp",
+ "t4t.cpp",
+ ":cts_hostsidetests_securitybulletin_memutils",
+ ],
+ compile_multilib: "64",
+ include_dirs: [
+ "system/nfc/src/fuzzers/rw/",
+ "system/nfc/src/include",
+ "system/nfc/src/gki/ulinux",
+ "system/nfc/src/gki/common",
+ "system/nfc/src/nfc/include",
+ "system/nfc/src/fuzzers/inc",
+ ],
+ shared_libs: [
+ "libnfc-nci",
+ "libchrome",
+ ],
+ cflags: [
+ "-DCHECK_OVERFLOW",
+ "-DENABLE_SELECTIVE_OVERLOADING",
+ ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/poc.cpp
new file mode 100644
index 00000000000..085f6e83d4a
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/poc.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+/* Following files are taken as reference to come up with this PoC */
+/* 1. 'system/nfc/src/fuzzers/fuzz_utils.cc' */
+/* 2. 'system/nfc/src/fuzzers/rw/main.cc' */
+
+#include <vector>
+#include "../includes/common.h"
+#include "fuzz.h"
+#include "fuzz_cmn.h"
+extern void Type4_Fuzz(uint8_t SubType, const std::vector<bytes_t> &Packets);
+
+FILE *file = nullptr;
+char *vulnPtr = nullptr;
+bool testInProgress = false;
+
+struct sigaction new_action, old_action;
+void sigsegv_handler(int signum, siginfo_t *info, void *context) {
+ if (testInProgress && info->si_signo == SIGSEGV) {
+ size_t pageSize = getpagesize();
+ if (pageSize) {
+ char *vulnPtrGuardPage = (char *)((size_t)vulnPtr & PAGE_MASK) + pageSize;
+ char *faultPage = (char *)((size_t)info->si_addr & PAGE_MASK);
+ if (faultPage == vulnPtrGuardPage) {
+ (*old_action.sa_sigaction)(signum, info, context);
+ return;
+ }
+ }
+ }
+ _exit(EXIT_FAILURE);
+}
+
+void exit_Handler(void) {
+ if (file) {
+ fclose(file);
+ }
+}
+
+static std::vector<bytes_t> UnpackPackets(const uint8_t *Data, size_t Size) {
+ std::vector<bytes_t> result;
+ while (Size > 0) {
+ auto s = *Data++;
+ Size--;
+
+ if (s > Size) {
+ s = Size;
+ }
+
+ if (s > 0) {
+ result.push_back(bytes_t(Data, Data + s));
+ }
+
+ Size -= s;
+ Data += s;
+ }
+
+ return result;
+}
+
+int main(int argc, char **argv) {
+ sigemptyset(&new_action.sa_mask);
+ new_action.sa_flags = SA_SIGINFO;
+ new_action.sa_sigaction = sigsegv_handler;
+ sigaction(SIGSEGV, &new_action, &old_action);
+ FAIL_CHECK(argc > 1);
+
+ file = fopen(argv[1], "r");
+ FAIL_CHECK(file);
+
+ fseek(file, 0, SEEK_END);
+ size_t size = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ std::vector<uint8_t> bufVector(size);
+ FAIL_CHECK(bufVector.data());
+ FAIL_CHECK(fread(bufVector.data(), 1, size, file) == size);
+
+ const uint8_t *data = (const uint8_t *)bufVector.data();
+ auto Packets = UnpackPackets(data, size);
+ FAIL_CHECK(Packets.size() > 1);
+
+ auto &ctrl = Packets[0];
+ FAIL_CHECK(ctrl.size() > 1);
+
+ Type4_Fuzz(ctrl[1], Packets);
+
+ return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/stubs.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/stubs.cpp
new file mode 100644
index 00000000000..d8072329371
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/stubs.cpp
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ */
+
+/* 'system/nfc/src/fuzzers/ce/stubs.cc' is used as reference to come up with file */
+
+#include "fuzz_cmn.h"
+
+// These are the functions implemented elsewhere in the NFC code. Our fuzzing
+// doesn't need them. To avoid pulling into more source code we simply stub
+// them out.
+
+tNFA_PROPRIETARY_CFG nfa_proprietary_cfg = {
+ 0x80, /* NCI_PROTOCOL_18092_ACTIVE */
+ 0x81, /* NCI_PROTOCOL_B_PRIME */
+ 0x82, /* NCI_PROTOCOL_DUAL */
+ 0x83, /* NCI_PROTOCOL_15693 */
+ 0x8A, /* NCI_PROTOCOL_KOVIO */
+ 0xFF, /* NCI_PROTOCOL_MIFARE */
+ 0x77, /* NCI_DISCOVERY_TYPE_POLL_KOVIO */
+ 0x74, /* NCI_DISCOVERY_TYPE_POLL_B_PRIME */
+ 0xF4, /* NCI_DISCOVERY_TYPE_LISTEN_B_PRIME */
+};
+
+tNFA_PROPRIETARY_CFG* p_nfa_proprietary_cfg = (tNFA_PROPRIETARY_CFG*)&nfa_proprietary_cfg;
+
+void nfc_start_quick_timer(TIMER_LIST_ENT*, uint16_t, uint32_t) {}
+void nfc_stop_timer(TIMER_LIST_ENT*) {}
+void nfc_stop_quick_timer(TIMER_LIST_ENT*) {}
+uint8_t NFC_GetNCIVersion() {
+ return NCI_VERSION_2_0;
+}
+
+tNFC_STATUS NFC_SendData(uint8_t conn_id, NFC_HDR* p_data) {
+ uint8_t* p = (uint8_t*)(p_data + 1) + p_data->offset;
+ uint8_t len = (uint8_t)p_data->len;
+
+ FUZZLOG("conn_id=%d, data=%s", conn_id, BytesToHex(p, len).c_str());
+ GKI_freebuf(p_data);
+ return NFC_STATUS_OK;
+}
+
+uint8_t nci_snd_t3t_polling(uint16_t system_code, uint8_t rc, uint8_t tsn) {
+ FUZZLOG("sc=%04X, rc=%02X, tsn=%02X", system_code, rc, tsn);
+ return NFC_STATUS_OK;
+}
+
+tNFC_CONN_CBACK* rf_cback = nullptr;
+void NFC_SetStaticRfCback(tNFC_CONN_CBACK* p_cback) {
+ rf_cback = p_cback;
+}
+
+tNFC_STATUS NFC_ISODEPNakPresCheck() {
+ return NFC_STATUS_OK;
+}
+
+std::string NFC_GetStatusName(tNFC_STATUS status) {
+ switch (status) {
+ case NFC_STATUS_OK:
+ return "OK";
+ case NFC_STATUS_REJECTED:
+ return "REJECTED";
+ case NFC_STATUS_MSG_CORRUPTED:
+ return "CORRUPTED";
+ case NFC_STATUS_BUFFER_FULL:
+ return "BUFFER_FULL";
+ case NFC_STATUS_FAILED:
+ return "FAILED";
+ case NFC_STATUS_NOT_INITIALIZED:
+ return "NOT_INITIALIZED";
+ case NFC_STATUS_SYNTAX_ERROR:
+ return "SYNTAX_ERROR";
+ case NFC_STATUS_SEMANTIC_ERROR:
+ return "SEMANTIC_ERROR";
+ case NFC_STATUS_UNKNOWN_GID:
+ return "UNKNOWN_GID";
+ case NFC_STATUS_UNKNOWN_OID:
+ return "UNKNOWN_OID";
+ case NFC_STATUS_INVALID_PARAM:
+ return "INVALID_PARAM";
+ case NFC_STATUS_MSG_SIZE_TOO_BIG:
+ return "MSG_SIZE_TOO_BIG";
+ case NFC_STATUS_ALREADY_STARTED:
+ return "ALREADY_STARTED";
+ case NFC_STATUS_ACTIVATION_FAILED:
+ return "ACTIVATION_FAILED";
+ case NFC_STATUS_TEAR_DOWN:
+ return "TEAR_DOWN";
+ case NFC_STATUS_RF_TRANSMISSION_ERR:
+ return "RF_TRANSMISSION_ERR";
+ case NFC_STATUS_RF_PROTOCOL_ERR:
+ return "RF_PROTOCOL_ERR";
+ case NFC_STATUS_TIMEOUT:
+ return "TIMEOUT";
+ case NFC_STATUS_EE_INTF_ACTIVE_FAIL:
+ return "EE_INTF_ACTIVE_FAIL";
+ case NFC_STATUS_EE_TRANSMISSION_ERR:
+ return "EE_TRANSMISSION_ERR";
+ case NFC_STATUS_EE_PROTOCOL_ERR:
+ return "EE_PROTOCOL_ERR";
+ case NFC_STATUS_EE_TIMEOUT:
+ return "EE_TIMEOUT";
+ case NFC_STATUS_CMD_STARTED:
+ return "CMD_STARTED";
+ case NFC_STATUS_HW_TIMEOUT:
+ return "HW_TIMEOUT";
+ case NFC_STATUS_CONTINUE:
+ return "CONTINUE";
+ case NFC_STATUS_REFUSED:
+ return "REFUSED";
+ case NFC_STATUS_BAD_RESP:
+ return "BAD_RESP";
+ case NFC_STATUS_CMD_NOT_CMPLTD:
+ return "CMD_CMPLTD";
+ case NFC_STATUS_NO_BUFFERS:
+ return "NO_BUFFERS";
+ case NFC_STATUS_WRONG_PROTOCOL:
+ return "WRONG_PROTOCOL";
+ case NFC_STATUS_BUSY:
+ return "BUSY";
+ case NFC_STATUS_LINK_LOSS:
+ return "LINK_LOSS";
+ case NFC_STATUS_BAD_LENGTH:
+ return "BAD_LENGTH";
+ case NFC_STATUS_BAD_HANDLE:
+ return "BAD_HANDLE";
+ case NFC_STATUS_CONGESTED:
+ return "CONGESTED";
+ default:
+ return "UNKNOWN";
+ }
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/t4t.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/t4t.cpp
new file mode 100644
index 00000000000..5746a9dea8f
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/t4t.cpp
@@ -0,0 +1,207 @@
+/*
+ * 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.
+ */
+
+/* 'system/nfc/src/fuzzers/rw/t4t.cc' is used as reference to come up with file */
+
+#include "fuzz.h"
+
+#include "../includes/memutils.h"
+char enable_selective_overload = ENABLE_NONE;
+extern bool testInProgress;
+extern char* vulnPtr;
+
+#define MODULE_NAME "Type4 Read/Write"
+
+enum {
+ SUB_TYPE_DETECT_NDEF,
+ SUB_TYPE_READ_NDEF,
+ SUB_TYPE_UPDATE_NDEF,
+ SUB_TYPE_PRESENCE_CHECK,
+ SUB_TYPE_SET_READ_ONLY,
+ SUB_TYPE_FORMAT_NDEF,
+
+ SUB_TYPE_MAX
+};
+
+static void rw_cback(tRW_EVENT event, tRW_DATA* p_rw_data) {
+ FUZZLOG(MODULE_NAME ": rw_cback: event=0x%02x, p_rw_data=%p", event, p_rw_data);
+
+ if (event == RW_T4T_RAW_FRAME_EVT) {
+ if (p_rw_data->raw_frame.p_data) {
+ GKI_freebuf(p_rw_data->raw_frame.p_data);
+ p_rw_data->raw_frame.p_data = nullptr;
+ }
+ } else if (event == RW_T4T_NDEF_READ_EVT || event == RW_T4T_NDEF_READ_CPLT_EVT) {
+ if (p_rw_data->data.p_data) {
+ GKI_freebuf(p_rw_data->data.p_data);
+ p_rw_data->data.p_data = nullptr;
+ }
+ }
+}
+
+#define TEST_NFCID_VALUE \
+ { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 }
+
+static bool Init(Fuzz_Context& /*ctx*/) {
+ tNFC_ACTIVATE_DEVT activate_params = {.protocol = NFC_PROTOCOL_ISO_DEP,
+ .rf_tech_param = {
+ .mode = NFC_DISCOVERY_TYPE_POLL_A,
+ }};
+
+ rw_init();
+ if (NFC_STATUS_OK != RW_SetActivatedTagType(&activate_params, rw_cback)) {
+ FUZZLOG(MODULE_NAME ": RW_SetActivatedTagType failed");
+ return false;
+ }
+
+ return true;
+}
+
+static bool Init_PresenceCheck(Fuzz_Context& /*ctx*/) {
+ return NFC_STATUS_OK == RW_T4tPresenceCheck(1);
+}
+
+static bool Init_DetectNDef(Fuzz_Context& /*ctx*/) {
+ return NFC_STATUS_OK == RW_T4tDetectNDef();
+}
+
+static bool Init_ReadNDef(Fuzz_Context& /*ctx*/) {
+ return NFC_STATUS_OK == RW_T4tReadNDef();
+}
+
+static bool Init_UpdateNDef(Fuzz_Context& ctx) {
+ const uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04,
+ 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04};
+
+ auto scratch = ctx.GetBuffer(sizeof(data), data);
+ return NFC_STATUS_OK == RW_T4tUpdateNDef(sizeof(data), scratch);
+}
+
+static bool Init_FormatNDef(Fuzz_Context& /*ctx*/) {
+ return NFC_STATUS_OK == RW_T4tFormatNDef();
+}
+
+static bool Init_SetNDefReadOnly(Fuzz_Context& /*ctx*/) {
+ return NFC_STATUS_OK == RW_T4tSetNDefReadOnly();
+}
+
+static bool Fuzz_Init(Fuzz_Context& ctx) {
+ if (!Init(ctx)) {
+ FUZZLOG(MODULE_NAME ": initialization failed");
+ return false;
+ }
+
+ bool result = false;
+ switch (ctx.SubType) {
+ case SUB_TYPE_DETECT_NDEF:
+ result = Init_DetectNDef(ctx);
+ break;
+ case SUB_TYPE_UPDATE_NDEF:
+ result = Init_UpdateNDef(ctx);
+ break;
+ case SUB_TYPE_PRESENCE_CHECK:
+ result = Init_PresenceCheck(ctx);
+ break;
+ case SUB_TYPE_READ_NDEF:
+ result = Init_ReadNDef(ctx);
+ break;
+ case SUB_TYPE_FORMAT_NDEF:
+ result = Init_FormatNDef(ctx);
+ break;
+ case SUB_TYPE_SET_READ_ONLY:
+ result = Init_SetNDefReadOnly(ctx);
+ break;
+ default:
+ FUZZLOG(MODULE_NAME ": Unknown command %d", ctx.SubType);
+ result = false;
+ break;
+ }
+
+ if (!result) {
+ FUZZLOG(MODULE_NAME ": Initializing command %02X failed", ctx.SubType);
+ }
+
+ return result;
+}
+
+static void Fuzz_Deinit(Fuzz_Context& /*ctx*/) {
+ if (rf_cback) {
+ tNFC_CONN conn = {.deactivate = {.status = NFC_STATUS_OK,
+ .type = NFC_DEACTIVATE_TYPE_IDLE,
+ .is_ntf = true,
+ .reason = NFC_DEACTIVATE_REASON_DH_REQ_FAILED}};
+
+ rf_cback(NFC_RF_CONN_ID, NFC_DEACTIVATE_CEVT, &conn);
+ }
+}
+
+static void Fuzz_Run(Fuzz_Context& ctx) {
+ for (auto it = ctx.Data.cbegin() + 1; it != ctx.Data.cend(); ++it) {
+ NFC_HDR* p_msg;
+
+ /* CVE-2021-0925 starts */
+ enable_selective_overload = ENABLE_ALL;
+ /* CVE-2021-0925 ends */
+
+ p_msg = (NFC_HDR*)GKI_getbuf(sizeof(NFC_HDR) + it->size());
+
+ /* CVE-2021-0925 starts */
+ vulnPtr = (char*)p_msg;
+ enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK;
+ /* CVE-2021-0925 ends */
+
+ if (p_msg == nullptr) {
+ FUZZLOG(MODULE_NAME ": GKI_getbuf returns null, size=%zu", it->size());
+ return;
+ }
+
+ /* Initialize NFC_HDR */
+ p_msg->len = it->size();
+ p_msg->offset = 0;
+
+ uint8_t* p = (uint8_t*)(p_msg + 1) + p_msg->offset;
+ memcpy(p, it->data(), it->size());
+
+ tNFC_CONN conn = {.data = {
+ .status = NFC_STATUS_OK,
+ .p_data = p_msg,
+ }};
+
+ FUZZLOG(MODULE_NAME ": SubType=%02X, Response[%zd/%zd]=%s", ctx.SubType,
+ it - ctx.Data.cbegin(), ctx.Data.size() - 1, BytesToHex(*it).c_str());
+
+ /* CVE-2021-0925 starts */
+ testInProgress = true;
+ /* CVE-2021-0925 ends */
+
+ rf_cback(NFC_RF_CONN_ID, NFC_DATA_CEVT, &conn);
+
+ /* CVE-2021-0925 starts */
+ testInProgress = false;
+ /* CVE-2021-0925 ends */
+ }
+}
+
+void Type4_FixPackets(uint8_t /*SubType*/, std::vector<bytes_t>& /*Data*/) {}
+
+void Type4_Fuzz(uint8_t SubType, const std::vector<bytes_t>& Data) {
+ Fuzz_Context ctx(SubType % SUB_TYPE_MAX, Data);
+ if (Fuzz_Init(ctx)) {
+ Fuzz_Run(ctx);
+ }
+
+ Fuzz_Deinit(ctx);
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
index 2d39dd1d9f6..c8e8cbfe049 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
@@ -492,7 +492,7 @@ public class AdbUtils {
*/
public static void runPocAssertExitStatusNotVulnerable(String pocName, String arguments,
ITestDevice device, int timeout) throws Exception {
- runPocGetExitStatus(pocName, arguments, null, device, timeout);
+ runPocAssertExitStatusNotVulnerable(pocName, arguments, null, device, timeout);
}
/**
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9549.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9549.java
new file mode 100644
index 00000000000..bf2b0d1d3f9
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9549.java
@@ -0,0 +1,44 @@
+/**
+ * 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.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2018_9549 extends SecurityTestCase {
+
+ /**
+ * b/112160868
+ * Vulnerability Behaviour: SIGABRT in self
+ */
+ @AsbSecurityTest(cveBugId = 112160868)
+ @Test
+ public void testPocCVE_2018_9549() throws Exception {
+ String binaryName = "CVE-2018-9549";
+ String signals[] = {CrashUtils.SIGABRT};
+ AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+ testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName);
+ testConfig.config.setSignals(signals);
+ testConfig.config
+ .setAbortMessageIncludes(AdbUtils.escapeRegexSpecialChars("ubsan: mul-overflow"));
+ AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2014.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2014.java
index afc7a2bb65c..e6863ac86a7 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2014.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2014.java
@@ -34,7 +34,7 @@ public class CVE_2019_2014 extends SecurityTestCase {
public void testPocCVE_2019_2014() throws Exception {
pocPusher.only64();
String binaryName = "CVE-2019-2014";
- String signals[] = {CrashUtils.SIGSEGV, CrashUtils.SIGBUS, CrashUtils.SIGABRT};
+ String signals[] = {CrashUtils.SIGABRT};
AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName);
testConfig.config.setSignals(signals);
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2027.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2027.java
new file mode 100644
index 00000000000..df6c6f4b6b2
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2027.java
@@ -0,0 +1,44 @@
+/**
+ * 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.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2019_2027 extends SecurityTestCase {
+
+ /**
+ * b/119120561
+ * Vulnerability Behaviour: SIGABRT in self
+ */
+ @AsbSecurityTest(cveBugId = 119120561)
+ @Test
+ public void testPocCVE_2019_2027() throws Exception {
+ String binaryName = "CVE-2019-2027";
+ String signals[] = {CrashUtils.SIGABRT};
+ AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+ testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName);
+ testConfig.config.setSignals(signals);
+ testConfig.config
+ .setAbortMessageIncludes(AdbUtils.escapeRegexSpecialChars("ubsan: mul-overflow"));
+ AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+ }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0925.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0925.java
new file mode 100644
index 00000000000..617658973bb
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0925.java
@@ -0,0 +1,43 @@
+/*
+ * 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.security.cts;
+import android.platform.test.annotations.AsbSecurityTest;
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_0925 extends SecurityTestCase {
+
+ /**
+ * Vulnerability Behaviour: SIGSEGV in self
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = 191444150)
+ public void testPocCVE_2021_0925() throws Exception {
+ pocPusher.only64();
+ String binaryName = "CVE-2021-0925";
+ String inputFiles[] = {"cve_2021_0925"};
+ String signals[] = {CrashUtils.SIGSEGV};
+ AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+ testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName);
+ testConfig.config.setSignals(signals);
+ testConfig.arguments = AdbUtils.TMP_PATH + inputFiles[0];
+ AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+ }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0921/src/android/security/cts/CVE_2021_0921/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0921/src/android/security/cts/CVE_2021_0921/DeviceTest.java
index bb6631ad9d0..233fdb44728 100644
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0921/src/android/security/cts/CVE_2021_0921/DeviceTest.java
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0921/src/android/security/cts/CVE_2021_0921/DeviceTest.java
@@ -19,6 +19,7 @@ package android.security.cts.CVE_2021_0921;
import org.junit.Test;
import org.junit.runner.RunWith;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -31,6 +32,7 @@ import androidx.test.uiautomator.UiDevice;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeNoException;
@RunWith(AndroidJUnit4.class)
public class DeviceTest {
@@ -54,6 +56,12 @@ public class DeviceTest {
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
+ try{
+ context.startActivity(intent);
+ } catch(ActivityNotFoundException e){
+ assumeNoException(e);
+ return;
+ }
//wait for poc app to complete (it takes about 6 seconds)
SystemClock.sleep(20000);
diff --git a/tests/PhotoPicker/Android.bp b/tests/PhotoPicker/Android.bp
new file mode 100644
index 00000000000..dac10dca7c8
--- /dev/null
+++ b/tests/PhotoPicker/Android.bp
@@ -0,0 +1,35 @@
+// 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 {
+ name: "CtsPhotoPickerTest",
+ manifest: "AndroidManifest.xml",
+ test_config: "AndroidTest.xml",
+ srcs: ["src/**/*.java", "helper/**/*.java", ":CtsProviderTestUtils",],
+ compile_multilib: "both",
+ test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+ sdk_version: "test_current",
+ min_sdk_version: "30",
+ libs: ["android.test.base", "android.test.runner",],
+ static_libs: [
+ "androidx.test.rules",
+ "cts-install-lib",
+ "androidx.test.uiautomator_uiautomator",
+ "Harrier",
+ ],
+}
diff --git a/tests/PhotoPicker/AndroidManifest.xml b/tests/PhotoPicker/AndroidManifest.xml
new file mode 100644
index 00000000000..ea26ad1424e
--- /dev/null
+++ b/tests/PhotoPicker/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.photopicker.cts">
+<application android:label="Photo Picker Device Tests">
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.photopicker.cts.GetResultActivity" />
+</application>
+
+<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.photopicker.cts"
+ android:label="Device-only photo picker tests" />
+
+</manifest>
diff --git a/tests/PhotoPicker/AndroidTest.xml b/tests/PhotoPicker/AndroidTest.xml
new file mode 100644
index 00000000000..e7f7163f688
--- /dev/null
+++ b/tests/PhotoPicker/AndroidTest.xml
@@ -0,0 +1,41 @@
+<?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.
+ -->
+<configuration description="Runs device-only tests for photo picker">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsPhotoPickerTest.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="cts" />
+ <option name="test-tag" value="PhotoPickerTests" />
+ <!-- Instant apps cannot access external storage -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.photopicker.cts" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
+ </test>
+ <option name="config-descriptor:metadata" key="parameter" value="multiuser" />
+
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+ </object>
+</configuration>
diff --git a/tests/PhotoPicker/OWNERS b/tests/PhotoPicker/OWNERS
new file mode 100644
index 00000000000..79add9eb349
--- /dev/null
+++ b/tests/PhotoPicker/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 95221
+
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg b/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg
new file mode 100644
index 00000000000..d26419604ef
--- /dev/null
+++ b/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg
Binary files differ
diff --git a/tests/PhotoPicker/res/raw/testvideo_meta.mp4 b/tests/PhotoPicker/res/raw/testvideo_meta.mp4
new file mode 100644
index 00000000000..e83c61db02f
--- /dev/null
+++ b/tests/PhotoPicker/res/raw/testvideo_meta.mp4
Binary files differ
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java b/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java
new file mode 100644
index 00000000000..35e7830f083
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java
@@ -0,0 +1,84 @@
+/*
+ * 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.photopicker.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GetResultActivity extends Activity {
+ private static LinkedBlockingQueue<Result> sResult;
+
+ public static class Result {
+ public final int requestCode;
+ public final int resultCode;
+ public final Intent data;
+
+ public Result(int requestCode, int resultCode, Intent data) {
+ this.requestCode = requestCode;
+ this.resultCode = resultCode;
+ this.data = data;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ try {
+ sResult.offer(new Result(requestCode, resultCode, data), 5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void clearResult() {
+ sResult = new LinkedBlockingQueue<>();
+ }
+
+ public Result getResult() {
+ final Result result;
+ try {
+ result = sResult.poll(40, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ if (result == null) {
+ throw new IllegalStateException("Activity didn't receive a Result in 40 seconds");
+ }
+ return result;
+ }
+
+ public Result getResult(long timeout, TimeUnit unit) {
+ try {
+ return sResult.poll(timeout, unit);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java
new file mode 100644
index 00000000000..e51ff082ce3
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.photopicker.cts;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
+
+import org.junit.Before;
+
+/**
+ * Photo Picker Base class for Photo Picker tests. This includes common setup methods
+ * required for all Photo Picker tests.
+ */
+public class PhotoPickerBaseTest {
+ public static int REQUEST_CODE = 42;
+
+ protected GetResultActivity mActivity;
+ protected Context mContext;
+ protected UiDevice mDevice;
+
+ @Before
+ public void setUp() throws Exception {
+ final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+ mContext = inst.getContext();
+ final Intent intent = new Intent(mContext, GetResultActivity.class);
+ 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");
+
+ mActivity = (GetResultActivity) inst.startActivitySync(intent);
+ // Wait for the UI Thread to become idle.
+ inst.waitForIdleSync();
+ mActivity.clearResult();
+ mDevice.waitForIdle();
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java
new file mode 100644
index 00000000000..b574648e6f6
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.photopicker.cts;
+
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findProfileButton;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ClipData;
+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;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
+
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+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();
+
+ private List<Uri> mUriList = new ArrayList<>();
+
+ @After
+ public void tearDown() throws Exception {
+ for (Uri uri : mUriList) {
+ deleteMedia(uri, mContext.getUserId());
+ }
+ mActivity.finish();
+ }
+
+ @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);
+
+ Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, imageCount);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ // Click the profile button to change to personal profile
+ final UiObject profileButton = findProfileButton();
+ profileButton.click();
+ mDevice.waitForIdle();
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ 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();
+ }
+
+ final UiObject addButton = findAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(imageCount);
+ for (int i = 0; i < count; i++) {
+ Uri uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, sDeviceState.primaryUser().id());
+ assertRedactedReadOnlyAccess(uri);
+ }
+ }
+
+ @Test
+ @EnsureHasWorkProfile
+ public void testPersonalApp_canAccessWorkProfileContents() throws Exception {
+ final int imageCount = 2;
+ createImages(imageCount, sDeviceState.workProfile().id(), mUriList);
+
+ Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, imageCount);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ // Click the profile button to change to work profile
+ final UiObject profileButton = findProfileButton();
+ profileButton.click();
+ mDevice.waitForIdle();
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ 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();
+ }
+
+ final UiObject addButton = findAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(imageCount);
+ for (int i = 0; i < count; i++) {
+ Uri uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, sDeviceState.workProfile().id());
+ assertRedactedReadOnlyAccess(uri);
+ }
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
new file mode 100644
index 00000000000..ed76f8d2fa9
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.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.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.findAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddButton;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.content.ClipData;
+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;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+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 static final String TAG = "PhotoPickerTest";
+ private List<Uri> mUriList = new ArrayList<>();
+
+ @After
+ public void tearDown() throws Exception {
+ for (Uri uri : mUriList) {
+ deleteMedia(uri, mContext.getUserId());
+ }
+ mActivity.finish();
+ }
+
+ @Test
+ public void testSingleSelect() throws Exception {
+ final int itemCount = 1;
+ createImages(itemCount, mContext.getUserId(), mUriList);
+
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final UiObject item = findItemList(itemCount).get(0);
+ item.click();
+ mDevice.waitForIdle();
+
+ final UiObject addButton = findPreviewAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final Uri uri = mActivity.getResult().data.getData();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertRedactedReadOnlyAccess(uri);
+ }
+
+ @Test
+ public void testMultiSelect_invalidParam() throws Exception {
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit() + 1);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+ final GetResultActivity.Result res = mActivity.getResult();
+ assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void testMultiSelect_invalidNegativeParam() throws Exception {
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, -1);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+ final GetResultActivity.Result res = mActivity.getResult();
+ assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void testMultiSelect_returnsNotMoreThanMax() throws Exception {
+ final int maxCount = 2;
+ final int imageCount = maxCount + 1;
+ createImages(imageCount, mContext.getUserId(), mUriList);
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxCount);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(imageCount);
+ // Select maxCount + 1 items
+ for (int i = 0; i < itemCount; i++) {
+ final UiObject item = itemList.get(i);
+ item.click();
+ mDevice.waitForIdle();
+ }
+
+ UiObject snackbarTextView = mDevice.findObject(new UiSelector().text(
+ "Select up to 2 items"));
+ assertWithMessage("Timed out while waiting for snackbar to appear").that(
+ snackbarTextView.waitForExists(SHORT_TIMEOUT)).isTrue();
+
+ assertWithMessage("Timed out waiting for snackbar to disappear").that(
+ snackbarTextView.waitUntilGone(SHORT_TIMEOUT)).isTrue();
+
+ final UiObject addButton = findAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(maxCount);
+ }
+
+ @Test
+ public void testMultiSelect_doesNotRespectExtraAllowMultiple() throws Exception {
+ final int imageCount = 2;
+ createImages(imageCount, mContext.getUserId(), mUriList);
+ final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ final List<UiObject> itemList = findItemList(imageCount);
+ final int itemCount = itemList.size();
+ assertThat(itemCount).isEqualTo(imageCount);
+ // Select 1 items
+ final UiObject item = itemList.get(0);
+ item.click();
+ mDevice.waitForIdle();
+
+ // Shows preview Add button; single select flow
+ final UiObject addButton = findPreviewAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final Uri uri = mActivity.getResult().data.getData();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertRedactedReadOnlyAccess(uri);
+ }
+
+ @Test
+ public void testMultiSelect() throws Exception {
+ final int imageCount = 4;
+ createImages(imageCount, mContext.getUserId(), mUriList);
+ 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(imageCount);
+ 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();
+ }
+
+ final UiObject addButton = findAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(itemCount);
+ for (int i = 0; i < count; i++) {
+ final Uri uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertRedactedReadOnlyAccess(uri);
+ }
+ }
+
+ @Test
+ public void testMimeTypeFilter() throws Exception {
+ final int videoCount = 2;
+ createVideos(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);
+ intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+ intent.setType(mimeType);
+ mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+ // find all items
+ final List<UiObject> itemList = findItemList(-1);
+ 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();
+ }
+
+ final UiObject addButton = findAddButton();
+ addButton.click();
+ mDevice.waitForIdle();
+
+ final ClipData clipData = mActivity.getResult().data.getClipData();
+ final int count = clipData.getItemCount();
+ assertThat(count).isEqualTo(itemCount);
+ for (int i = 0; i < count; i++) {
+ final Uri uri = clipData.getItemAt(i).getUri();
+ assertPickerUriFormat(uri, mContext.getUserId());
+ assertRedactedReadOnlyAccess(uri);
+ assertMimeType(uri, mimeType);
+ }
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java
new file mode 100644
index 00000000000..28380f9a094
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java
@@ -0,0 +1,161 @@
+/*
+ * 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.photopicker.cts.util;
+
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.DISPLAY_NAME_PREFIX;
+import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE;
+import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * Photo Picker Utility methods for test assertions.
+ */
+public class PhotoPickerAssertionsUtils {
+ private static final String TAG = "PhotoPickerTestAssertions";
+
+ public static void assertPickerUriFormat(Uri uri, int expectedUserId) {
+ // content://media/picker/<user-id>/<media-id>
+ final int userId = Integer.parseInt(uri.getPathSegments().get(1));
+ assertThat(userId).isEqualTo(expectedUserId);
+
+ final String auth = uri.getPathSegments().get(0);
+ assertThat(auth).isEqualTo("picker");
+ }
+
+ public static void assertMimeType(Uri uri, String expectedMimeType) throws Exception {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final String resultMimeType = context.getContentResolver().getType(uri);
+ assertThat(resultMimeType).isEqualTo(expectedMimeType);
+ }
+
+ public static void assertRedactedReadOnlyAccess(Uri uri) throws Exception {
+ assertThat(uri).isNotNull();
+ final String[] projection = new String[]{MediaStore.Files.FileColumns.TITLE,
+ MediaStore.Files.FileColumns.MEDIA_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();
+
+ boolean canCheckRedacted = false;
+ // If the file is inserted by this test case, we can check the redaction.
+ // To avoid checking the redaction on the other media file.
+ if (c.getString(0).startsWith(DISPLAY_NAME_PREFIX)) {
+ canCheckRedacted = true;
+ } else {
+ Log.d(TAG, uri + " is not the test file we expected, don't check the redaction");
+ }
+
+ final int mediaType = c.getInt(1);
+ switch (mediaType) {
+ case MEDIA_TYPE_IMAGE:
+ assertImageRedactedReadOnlyAccess(uri, canCheckRedacted, resolver);
+ break;
+ case MEDIA_TYPE_VIDEO:
+ assertVideoRedactedReadOnlyAccess(uri, canCheckRedacted, resolver);
+ break;
+ default:
+ fail("The media type is not as expected: " + mediaType);
+ }
+ }
+
+ private static void assertVideoRedactedReadOnlyAccess(Uri uri, boolean shouldCheckRedacted,
+ ContentResolver resolver) throws Exception {
+ if (shouldCheckRedacted) {
+ // The location is redacted
+ try (InputStream in = resolver.openInputStream(uri);
+ ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ FileUtils.copy(in, out);
+ byte[] bytes = out.toByteArray();
+ byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
+ String xmp = new String(xmpBytes);
+ assertWithMessage("Failed to redact XMP longitude")
+ .that(xmp.contains("10,41.751000E")).isFalse();
+ assertWithMessage("Failed to redact XMP latitude")
+ .that(xmp.contains("53,50.070500N")).isFalse();
+ assertWithMessage("Redacted non-location XMP")
+ .that(xmp.contains("13166/7763")).isTrue();
+ }
+ }
+
+ try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r")) {
+ // this should pass
+ }
+
+ // assert no write access
+ try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
+ fail("Does not grant write access to uri " + uri.toString());
+ } catch (SecurityException | FileNotFoundException expected) {
+ }
+ }
+
+ private static void assertImageRedactedReadOnlyAccess(Uri uri, boolean shouldCheckRedacted,
+ ContentResolver resolver) throws Exception {
+ if (shouldCheckRedacted) {
+ // The location is redacted
+ try (InputStream is = resolver.openInputStream(uri)) {
+ final ExifInterface exif = new ExifInterface(is);
+ final float[] latLong = new float[2];
+ exif.getLatLong(latLong);
+ assertWithMessage("Failed to redact latitude")
+ .that(latLong[0]).isWithin(0.001f).of(0);
+ assertWithMessage("Failed to redact longitude")
+ .that(latLong[1]).isWithin(0.001f).of(0);
+
+ String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
+ assertWithMessage("Failed to redact XMP longitude")
+ .that(xmp.contains("10,41.751000E")).isFalse();
+ assertWithMessage("Failed to redact XMP latitude")
+ .that(xmp.contains("53,50.070500N")).isFalse();
+ assertWithMessage("Redacted non-location XMP")
+ .that(xmp.contains("LensDefaults")).isTrue();
+ }
+ }
+
+ try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r")) {
+ // this should pass
+ }
+
+ // assert no write access
+ try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
+ fail("Does not grant write access to uri " + uri.toString());
+ } catch (SecurityException | FileNotFoundException expected) {
+ }
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java
new file mode 100644
index 00000000000..37a44f99915
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java
@@ -0,0 +1,116 @@
+/*
+ * 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.photopicker.cts.util;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.UserHandle;
+import android.photopicker.cts.R;
+import android.provider.MediaStore;
+import android.provider.cts.media.MediaStoreUtils;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * Photo Picker Utility methods for media files creation and deletion.
+ */
+public class PhotoPickerFilesUtils {
+ public static final String DISPLAY_NAME_PREFIX = "ctsPhotoPicker";
+
+ public static void createImages(int count, int userId, List<Uri> uriList)
+ throws Exception {
+ for (int i = 0; i < count; i++) {
+ final Uri uri = createImage(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++) {
+ final Uri uri = createVideo(userId);
+ uriList.add(uri);
+ clearMediaOwner(uri, userId);
+ }
+ }
+
+ public static void deleteMedia(Uri uri, int userId) throws Exception {
+ final String cmd = String.format("content delete --uri %s --user %d", uri, userId);
+ ShellUtils.runShellCommand(cmd);
+ }
+
+ private static void clearMediaOwner(Uri uri, int userId) throws Exception {
+ final String cmd = String.format(
+ "content update --uri %s --user %d --bind owner_package_name:n:", uri, userId);
+ ShellUtils.runShellCommand(cmd);
+ }
+
+ private static Uri createVideo(int userId) throws Exception {
+ final Uri uri = stageMedia(R.raw.testvideo_meta,
+ MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId);
+ return uri;
+ }
+
+ private static Uri createImage(int userId) throws Exception {
+ final Uri uri = stageMedia(R.raw.lg_g4_iso_800_jpg,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/jpeg", userId);
+ return uri;
+ }
+
+ private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, int userId) throws
+ Exception {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ uiAutomation.adoptShellPermissionIdentity(
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ try {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final Context userContext = userId == context.getUserId() ? context :
+ context.createPackageContextAsUser("android", /* flags= */ 0,
+ UserHandle.of(userId));
+ return stageMedia(resId, collectionUri, mimeType, userContext);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, Context context)
+ throws IOException {
+ final String displayName = DISPLAY_NAME_PREFIX + System.nanoTime();
+ final MediaStoreUtils.PendingParams params = new MediaStoreUtils.PendingParams(
+ collectionUri, displayName, mimeType);
+ final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+ try (MediaStoreUtils.PendingSession session = MediaStoreUtils.openPending(context,
+ pendingUri)) {
+ try (InputStream source = context.getResources().openRawResource(resId);
+ OutputStream target = session.openOutputStream()) {
+ FileUtils.copy(source, target);
+ }
+ return session.publish();
+ }
+ }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java
new file mode 100644
index 00000000000..e5912434a98
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java
@@ -0,0 +1,90 @@
+/*
+ * 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.photopicker.cts.util;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.text.format.DateUtils;
+
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiScrollable;
+import androidx.test.uiautomator.UiSelector;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker Utility methods for finding UI elements.
+ */
+public class PhotoPickerUiUtils {
+ public static final long SHORT_TIMEOUT = 5 * DateUtils.SECOND_IN_MILLIS;
+
+ private static final long TIMEOUT = 30 * DateUtils.SECOND_IN_MILLIS;
+ private static final String REGEX_PACKAGE_NAME =
+ "com(.google)?.android.providers.media(.module)?";
+
+ /**
+ * Get the list of items from the photo grid list.
+ * @param itemCount if the itemCount is -1, return all matching items. Otherwise, return the
+ * item list that its size is not greater than the itemCount.
+ * @throws Exception
+ */
+ public static List<UiObject> findItemList(int itemCount) throws Exception {
+ final List<UiObject> itemList = new ArrayList<>();
+ final UiSelector gridList = new UiSelector().className(
+ "androidx.recyclerview.widget.RecyclerView").resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/picker_tab_recyclerview");
+
+ // Wait for the first item to appear
+ assertWithMessage("Timed out while waiting for first item to appear")
+ .that(new UiObject(gridList.childSelector(new UiSelector())).waitForExists(TIMEOUT))
+ .isTrue();
+
+ final UiSelector itemSelector = new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/icon_thumbnail");
+ final UiScrollable grid = new UiScrollable(gridList);
+ final int childCount = grid.getChildCount();
+ final int count = itemCount == -1 ? childCount : itemCount;
+
+ for (int i = 0; i < childCount; i++) {
+ final UiObject item = grid.getChildByInstance(itemSelector, i);
+ if (item.exists()) {
+ itemList.add(item);
+ }
+ if (itemList.size() == count) {
+ break;
+ }
+ }
+ return itemList;
+ }
+
+ public static UiObject findPreviewAddButton() throws UiObjectNotFoundException {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/preview_add_button"));
+ }
+
+ public static UiObject findAddButton() throws UiObjectNotFoundException {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/button_add"));
+ }
+
+ public static UiObject findProfileButton() throws UiObjectNotFoundException {
+ return new UiObject(new UiSelector().resourceIdMatches(
+ REGEX_PACKAGE_NAME + ":id/profile_button"));
+ }
+}