diff options
37 files changed, 895 insertions, 106 deletions
diff --git a/hostsidetests/scopedstorage/AndroidTest.xml b/hostsidetests/scopedstorage/AndroidTest.xml index 42a3a362933..320d5417f17 100644 --- a/hostsidetests/scopedstorage/AndroidTest.xml +++ b/hostsidetests/scopedstorage/AndroidTest.xml @@ -28,6 +28,12 @@ <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" /> <option name="test-file-name" value="CtsLegacyStorageTestAppRequestLegacy.apk" /> </target_preparer> + + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + <test class="com.android.tradefed.testtype.HostTest" > <option name="class" value="android.scopedstorage.cts.host.LegacyStorageHostTest" /> <option name="class" value="android.scopedstorage.cts.host.PreserveLegacyStorageHostTest" /> diff --git a/hostsidetests/scopedstorage/CoreTest.xml b/hostsidetests/scopedstorage/CoreTest.xml index 5b725e1a853..325807d45c3 100644 --- a/hostsidetests/scopedstorage/CoreTest.xml +++ b/hostsidetests/scopedstorage/CoreTest.xml @@ -27,6 +27,12 @@ <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" /> <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" /> </target_preparer> + + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + <test class="com.android.tradefed.testtype.HostTest" > <option name="class" value="android.scopedstorage.cts.host.ScopedStorageCoreHostTest" /> </test> diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java index a93aeee3c98..ee63a8abf13 100644 --- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java +++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java @@ -24,6 +24,7 @@ import static android.scopedstorage.cts.lib.TestUtils.CHECK_DATABASE_ROW_EXISTS_ import static android.scopedstorage.cts.lib.TestUtils.CREATE_FILE_QUERY; import static android.scopedstorage.cts.lib.TestUtils.CREATE_IMAGE_ENTRY_QUERY; import static android.scopedstorage.cts.lib.TestUtils.DELETE_FILE_QUERY; +import static android.scopedstorage.cts.lib.TestUtils.DELETE_RECURSIVE_QUERY; import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXCEPTION; import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_CALLING_PKG; import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_PATH; @@ -40,6 +41,7 @@ import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_PARAMS_SEPARAT import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_QUERY; import static android.scopedstorage.cts.lib.TestUtils.SETATTR_QUERY; import static android.scopedstorage.cts.lib.TestUtils.canOpen; +import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively; import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase; import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri; @@ -93,6 +95,7 @@ public class ScopedStorageTestHelper extends Activity { case CAN_READ_WRITE_QUERY: case CREATE_FILE_QUERY: case DELETE_FILE_QUERY: + case DELETE_RECURSIVE_QUERY: case CAN_OPEN_FILE_FOR_READ_QUERY: case CAN_OPEN_FILE_FOR_WRITE_QUERY: case OPEN_FILE_FOR_READ_QUERY: @@ -263,6 +266,9 @@ public class ScopedStorageTestHelper extends Activity { case DELETE_FILE_QUERY: intent.putExtra(queryType, file.delete()); return intent; + case DELETE_RECURSIVE_QUERY: + intent.putExtra(queryType, deleteRecursively(file)); + return intent; case SETATTR_QUERY: int newTimeMillis = 12345000; intent.putExtra(queryType, file.setLastModified(newTimeMillis)); diff --git a/hostsidetests/scopedstorage/device/AndroidTest.xml b/hostsidetests/scopedstorage/device/AndroidTest.xml index 7e6f8953ed5..5730b2e2826 100644 --- a/hostsidetests/scopedstorage/device/AndroidTest.xml +++ b/hostsidetests/scopedstorage/device/AndroidTest.xml @@ -23,6 +23,11 @@ <option name="test-file-name" value="CtsScopedStorageTestAppFileManager.apk" /> </target_preparer> + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + <option name="config-descriptor:metadata" key="component" value="framework" /> <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> 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 3c860132c50..382f74551f6 100644 --- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java +++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java @@ -29,8 +29,10 @@ import static android.scopedstorage.cts.lib.TestUtils.STR_DATA2; import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid; import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory; import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile; +import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories; import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameDirectory; import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile; +import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories; import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains; import static android.scopedstorage.cts.lib.TestUtils.assertFileContent; import static android.scopedstorage.cts.lib.TestUtils.assertMountMode; @@ -43,6 +45,7 @@ import static android.scopedstorage.cts.lib.TestUtils.createFileAs; import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs; import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow; import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively; +import static android.scopedstorage.cts.lib.TestUtils.deleteRecursivelyAs; import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProvider; import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow; import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid; @@ -208,7 +211,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { "CtsScopedStorageTestAppFileManager.apk"); // A legacy targeting app with RES and WES permissions private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy", - "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk"); + "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk"); // The following apps are not installed at test startup - please install before using. private static final TestApp APP_C = new TestApp("TestAppC", @@ -1159,7 +1162,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { try { // Delete the directory if it already exists if (podcastsDir.exists()) { - deleteAsLegacyApp(podcastsDir); + deleteRecursivelyAsLegacyApp(podcastsDir); } assertThat(podcastsDir.exists()).isFalse(); assertThat(podcastsDirLowerCase.exists()).isFalse(); @@ -2786,18 +2789,57 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { } } + /** + * Tests that System Gallery apps cannot insert files in other app's private directories. + */ + @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + int uid = Process.myUid(); + try { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS); + assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } finally { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS); + } + } + + /** + * Tests that System Gallery apps cannot update files in other app's private directories. + */ + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + int uid = Process.myUid(); + try { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS); + assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } finally { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS); + } + } + + /** + * This test is for operations to the calling app's own private packages. + */ @Test public void testInsertFromExternalDirsViaRelativePath() throws Exception { verifyInsertFromExternalMediaDirViaRelativePath_allowed(); verifyInsertFromExternalPrivateDirViaRelativePath_denied(); } + /** + * This test is for operations to the calling app's own private packages. + */ @Test public void testUpdateToExternalDirsViaRelativePath() throws Exception { verifyUpdateToExternalMediaDirViaRelativePath_allowed(); verifyUpdateToExternalPrivateDirsViaRelativePath_denied(); } + /** + * This test is for operations to the calling app's own private packages. + */ @Test public void testInsertFromExternalDirsViaRelativePathAsSystemGallery() throws Exception { int uid = Process.myUid(); @@ -2810,6 +2852,9 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { } } + /** + * This test is for operations to the calling app's own private packages. + */ @Test public void testUpdateToExternalDirsViaRelativePathAsSystemGallery() throws Exception { int uid = Process.myUid(); @@ -3336,4 +3381,14 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { Log.d(TAG, "Deleting file " + file); deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath()); } + + /** + * Deletes the given file/directory recursively. If the file is a directory, then deletes all + * of its children (files or directories) recursively. + */ + private void deleteRecursivelyAsLegacyApp(File dir) throws Exception { + // Use a legacy app to delete this directory, since it could be outside shared storage. + Log.d(TAG, "Deleting directory " + dir); + deleteRecursivelyAs(APP_D_LEGACY_HAS_RW, dir.getAbsolutePath()); + } } 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 75c3f0dd79f..5638e41c0de 100644 --- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java +++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java @@ -111,6 +111,46 @@ public class LegacyStorageHostTest extends BaseHostTestCase { } @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasRW() throws Exception { + runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasRW"); + } + + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasRW() throws Exception { + runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasRW"); + } + + @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasMES() throws Exception { + allowAppOps("android:manage_external_storage"); + try { + runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasMES"); + } finally { + denyAppOps("android:manage_external_storage"); + } + } + + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasMES() throws Exception { + allowAppOps("android:manage_external_storage"); + try { + runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasMES"); + } finally { + denyAppOps("android:manage_external_storage"); + } + } + + @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery"); + } + + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery"); + } + + @Test public void testMkdirInRandomPlaces_hasW() throws Exception { revokePermissions("android.permission.READ_EXTERNAL_STORAGE"); executeShellCommand("mkdir -p /sdcard/Android/data/com.android.shell -m 2770"); @@ -263,4 +303,18 @@ public class LegacyStorageHostTest extends BaseHostTestCase { public void testUpdateToExternalDirsViaRelativePath() throws Exception { runDeviceTest("testUpdateToExternalDirsViaRelativePath"); } + + private void allowAppOps(String... ops) throws Exception { + for (String op : ops) { + executeShellCommand("cmd appops set --uid android.scopedstorage.cts.legacy " + + op + " allow"); + } + } + + private void denyAppOps(String... ops) throws Exception { + for (String op : ops) { + executeShellCommand("cmd appops set --uid android.scopedstorage.cts.legacy " + + op + " deny"); + } + } } 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 d31bc33dfa9..cd9378d3047 100644 --- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java +++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java @@ -125,6 +125,26 @@ public class ScopedStorageHostTest extends BaseHostTestCase { } @Test + public void testManageExternalStorageCantInsertFilesInOtherAppPrivateDir() throws Exception { + allowAppOps("android:manage_external_storage"); + try { + runDeviceTest("testManageExternalStorageCantInsertFilesInOtherAppPrivateDir"); + } finally { + denyAppOps("android:manage_external_storage"); + } + } + + @Test + public void testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir() throws Exception { + allowAppOps("android:manage_external_storage"); + try { + runDeviceTest("testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir"); + } finally { + denyAppOps("android:manage_external_storage"); + } + } + + @Test public void testCheckInstallerAppAccessToObbDirs() throws Exception { allowAppOps("android:request_install_packages"); grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE"); diff --git a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml index c602f0ac9c0..c85b0903670 100644 --- a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml +++ b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml @@ -20,6 +20,7 @@ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <application android:requestLegacyExternalStorage="true" > <uses-library android:name="android.test.runner" /> 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 1c8b3f5ca89..e3b08bbb5e4 100644 --- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java +++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java @@ -23,7 +23,9 @@ import static android.scopedstorage.cts.lib.TestUtils.STR_DATA2; import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid; import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory; import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile; +import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories; import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile; +import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories; import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains; import static android.scopedstorage.cts.lib.TestUtils.assertFileContent; import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs; @@ -47,8 +49,10 @@ import static android.scopedstorage.cts.lib.TestUtils.insertFile; import static android.scopedstorage.cts.lib.TestUtils.insertFileFromExternalMedia; import static android.scopedstorage.cts.lib.TestUtils.listAs; import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState; +import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed; import static android.scopedstorage.cts.lib.TestUtils.pollForPermission; import static android.scopedstorage.cts.lib.TestUtils.resetDefaultExternalStorageVolume; +import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid; import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories; import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert; import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert; @@ -997,6 +1001,82 @@ public class LegacyStorageTest { } /** + * Tests that legacy apps cannot insert in other app private directory + */ + @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasRW() throws Exception { + pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /* granted */ true); + pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /* granted */ true); + + assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } + + /** + * Tests that legacy apps cannot update in other app private directory + */ + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasRW() throws Exception { + pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /* granted */ true); + pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /* granted */ true); + + TestUtils.assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } + + /** + * Tests that legacy apps with MANAGE_EXTERNAL_STORAGE cannot insert in other app private + * directory + */ + @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasMES() throws Exception { + pollForManageExternalStorageAllowed(); + assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } + + /** + * Tests that legacy apps with MANAGE_EXTERNAL_STORAGE cannot update in other app private + * directory + */ + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasMES() throws Exception { + pollForManageExternalStorageAllowed(); + assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } + + /** + * Tests that legacy System Gallery apps cannot insert in other app private directory + */ + @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + int uid = Process.myUid(); + try { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS); + assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } finally { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS); + } + } + + /** + * Tests that legacy System Gallery apps cannot update in other app private directory + */ + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + int uid = Process.myUid(); + try { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS); + assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } finally { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS); + } + } + + /** * Make sure inserting files from app private directories in legacy apps is allowed via DATA. */ @Test 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 30683328b35..a04b86fed22 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 @@ -16,6 +16,7 @@ package android.scopedstorage.cts.lib; +import static android.provider.MediaStore.VOLUME_EXTERNAL; import static android.scopedstorage.cts.lib.RedactionTestHelper.EXIF_METADATA_QUERY; import static androidx.test.InstrumentationRegistry.getContext; @@ -98,6 +99,7 @@ public class TestUtils { public static final String CREATE_IMAGE_ENTRY_QUERY = "android.scopedstorage.cts.createimageentry"; public static final String DELETE_FILE_QUERY = "android.scopedstorage.cts.deletefile"; + public static final String DELETE_RECURSIVE_QUERY = "android.scopedstorage.cts.deleteRecursive"; public static final String CAN_OPEN_FILE_FOR_READ_QUERY = "android.scopedstorage.cts.can_openfile_read"; public static final String CAN_OPEN_FILE_FOR_WRITE_QUERY = @@ -295,6 +297,17 @@ public class TestUtils { } /** + * Makes the given {@code testApp} delete a file or directory. + * If the file is a directory, then deletes all of its children (file or directories) + * recursively. + * + * <p>This method drops shell permission identity. + */ + public static boolean deleteRecursivelyAs(TestApp testApp, String path) throws Exception { + return getResultFromTestApp(testApp, path, DELETE_RECURSIVE_QUERY); + } + + /** * Makes the given {@code testApp} delete a file. Doesn't throw in case of failure. */ public static boolean deleteFileAsNoThrow(TestApp testApp, String path) { @@ -950,6 +963,111 @@ public class TestUtils { } /** + * Assert that app cannot insert files in other app's private directories + * + * @param fileName name of the file + * @param throwsExceptionForDataValue Apps like System Gallery for which Data column is not + * respected, will not throw an Exception as the Data value is ignored. + * @param otherApp Other test app in whose external private directory we will attempt to insert + * @param callingPackageName Calling package name + */ + public static void assertCantInsertToOtherPrivateAppDirectories(String fileName, + boolean throwsExceptionForDataValue, TestApp otherApp, String callingPackageName) + throws Exception { + // Create directory in which the device test will try to insert file to + final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace( + callingPackageName, otherApp.getPackageName())); + final File file = new File(otherAppExternalDataDir, fileName); + try { + assertThat(createFileAs(otherApp, file.getPath())).isTrue(); + + final ContentValues valuesWithData = new ContentValues(); + valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath()); + try { + Uri uri = getContentResolver().insert( + MediaStore.Files.getContentUri(VOLUME_EXTERNAL), + valuesWithData); + + if (throwsExceptionForDataValue) { + fail("File insert expected to fail: " + file); + } else { + try (Cursor c = getContentResolver().query(uri, new String[]{ + MediaStore.MediaColumns.DATA}, null, null)) { + assertThat(c.moveToFirst()).isTrue(); + assertThat(c.getString(0)).isNotEqualTo(file.getAbsolutePath()); + } + } + } catch (IllegalArgumentException expected) { + } + + final ContentValues valuesWithRelativePath = new ContentValues(); + final String path = file.getAbsolutePath(); + valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH, + path.substring(path.indexOf("Android"))); + valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); + try { + getContentResolver().insert(MediaStore.Files.getContentUri(VOLUME_EXTERNAL), + valuesWithRelativePath); + fail("File insert expected to fail: " + file); + } catch (IllegalArgumentException expected) { + } + } finally { + deleteFileAsNoThrow(otherApp, file.getPath()); + } + } + + /** + * Assert that app cannot update files in other app's private directories + * + * @param fileName name of the file + * @param throwsExceptionForDataValue Apps like non-legacy System Gallery/MES for which + * Data column is not respected, will not throw an Exception as the Data value is ignored. + * @param otherApp Other test app in whose external private directory we will attempt to insert + * @param callingPackageName Calling package name + */ + public static void assertCantUpdateToOtherPrivateAppDirectories(String fileName, + boolean throwsExceptionForDataValue, TestApp otherApp, String callingPackageName) + throws Exception { + // Create priv-app file and add to the database that we will try to update + final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace( + callingPackageName, otherApp.getPackageName())); + final File file = new File(otherAppExternalDataDir, fileName); + try { + assertThat(createFileAs(otherApp, file.getPath())).isTrue(); + MediaStore.scanFile(getContentResolver(), file); + + final ContentValues valuesWithData = new ContentValues(); + valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath()); + try { + int res = getContentResolver().update( + MediaStore.Files.getContentUri(VOLUME_EXTERNAL), + valuesWithData, Bundle.EMPTY); + + if (throwsExceptionForDataValue) { + fail("File update expected to fail: " + file); + } else { + assertThat(res).isEqualTo(0); + } + } catch (IllegalArgumentException expected) { + } + + final ContentValues valuesWithRelativePath = new ContentValues(); + final String path = file.getAbsolutePath(); + valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH, + path.substring(path.indexOf("Android"))); + valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); + try { + getContentResolver().update(MediaStore.Files.getContentUri(VOLUME_EXTERNAL), + valuesWithRelativePath, Bundle.EMPTY); + fail("File update expected to fail: " + file); + } catch (IllegalArgumentException expected) { + } + } finally { + deleteFileAsNoThrow(otherApp, file.getPath()); + } + } + + /** * Asserts can rename directory. */ public static void assertCanRenameDirectory(File oldDirectory, File newDirectory, diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java index 814f1a24654..ec4f4322ec0 100644 --- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java +++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java @@ -21,6 +21,8 @@ import static android.scopedstorage.cts.lib.TestUtils.adoptShellPermissionIdenti import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidDataDir; import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidObbDir; import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile; +import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories; +import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories; import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains; import static android.scopedstorage.cts.lib.TestUtils.assertFileContent; import static android.scopedstorage.cts.lib.TestUtils.assertMountMode; @@ -142,7 +144,7 @@ public class ScopedStorageTest { "CtsScopedStorageTestAppB.apk"); // A legacy targeting app with RES and WES permissions private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy", - "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk"); + "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk"); @Before public void setup() throws Exception { @@ -228,6 +230,28 @@ public class ScopedStorageTest { }); } + /** + * Tests that apps with MANAGE_EXTERNAL_STORAGE permission cannot insert files in other app's + * private directories. + */ + @Test + public void testManageExternalStorageCantInsertFilesInOtherAppPrivateDir() throws Exception { + pollForManageExternalStorageAllowed(); + assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* throwsExceptionForDataValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } + + /** + * Tests that apps with MANAGE_EXTERNAL_STORAGE permission cannot update files in other app's + * private directories. + */ + @Test + public void testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir() throws Exception { + pollForManageExternalStorageAllowed(); + assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } + @Test public void testManageExternalStorageCanDeleteOtherAppsContents() throws Exception { pollForManageExternalStorageAllowed(); @@ -305,7 +329,8 @@ public class ScopedStorageTest { final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace( THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName())); final File otherAppExternalDataSubDir = new File(otherAppExternalDataDir, "subdir"); - final File otherAppExternalDataFile = new File(otherAppExternalDataSubDir, "abc.jpg"); + final File otherAppExternalDataFile = + new File(otherAppExternalDataSubDir, IMAGE_FILE_NAME); assertThat(createFileAs(APP_B_NO_PERMS, otherAppExternalDataFile.getAbsolutePath())) .isTrue(); diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0922.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0922.java deleted file mode 100644 index d8f3ddfa1d3..00000000000 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0922.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 static org.junit.Assert.assertTrue; -import android.platform.test.annotations.AsbSecurityTest; -import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(DeviceJUnit4ClassRunner.class) -public class CVE_2021_0922 extends SecurityTestCase { - - /** - * b/195630721 - */ - @AsbSecurityTest(cveBugId = 195630721) - @Test - public void testPocCVE_2021_0922() throws Exception { - String packageName = "com.android.managedprovisioning"; - String queryStr = "dumpsys package " + packageName; - String permissions = AdbUtils.runCommandLine(queryStr, getDevice()); - - // MANAGE_APP_OPS_MODES permission must be enforced for - // package com.android.managedprovisioning - assertTrue(permissions.contains("android.permission.MANAGE_APP_OPS_MODES")); - } -} diff --git a/tests/MediaProviderTranscode/AndroidTest.xml b/tests/MediaProviderTranscode/AndroidTest.xml index 8dba7414c5c..7dc78ebcd2f 100644 --- a/tests/MediaProviderTranscode/AndroidTest.xml +++ b/tests/MediaProviderTranscode/AndroidTest.xml @@ -19,6 +19,11 @@ <option name="install-arg" value="-g" /> </target_preparer> + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + <option name="test-suite-tag" value="apct" /> <option name="test-suite-tag" value="cts" /> <option name="test-tag" value="MediaProviderTranscodeTests" /> diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java index e218d4d0aec..9b5904cefde 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java @@ -214,16 +214,17 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { @Test public void testMultiSelect_longPress() throws Exception { - final int imageCount = 3; - createImages(imageCount, mContext.getUserId(), mUriList); + final int videoCount = 3; + createVideos(videoCount, mContext.getUserId(), mUriList); final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit() intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100); + intent.setType("video/*"); mActivity.startActivityForResult(intent, REQUEST_CODE); - final List<UiObject> itemList = findItemList(imageCount); + final List<UiObject> itemList = findItemList(videoCount); final int itemCount = itemList.size(); - assertThat(itemCount).isEqualTo(imageCount); + assertThat(itemCount).isEqualTo(videoCount); // Select one item from Photo grid itemList.get(0).click(); diff --git a/tests/app/DownloadManagerApi28Test/src/android/app/cts/DownloadManagerApi28Test.java b/tests/app/DownloadManagerApi28Test/src/android/app/cts/DownloadManagerApi28Test.java index 8b7d348d8e6..1df830d8ff5 100644 --- a/tests/app/DownloadManagerApi28Test/src/android/app/cts/DownloadManagerApi28Test.java +++ b/tests/app/DownloadManagerApi28Test/src/android/app/cts/DownloadManagerApi28Test.java @@ -15,6 +15,8 @@ */ package android.app.cts; +import static com.android.compatibility.common.util.SystemUtil.runShellCommand; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; @@ -32,6 +34,7 @@ import android.os.FileUtils; import androidx.test.runner.AndroidJUnit4; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -99,6 +102,28 @@ public class DownloadManagerApi28Test extends DownloadManagerTestBase { } @Test + public void testSetDestinationUri_privateAppDir() throws Exception { + // Make sure the private app directory exists + runShellCommand("mkdir -p /sdcard/Android/data/com.android.shell -m 2770"); + final File path = new File("/sdcard/Android/data/com.android.shell/" + + TAG + System.currentTimeMillis()); + + final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); + try { + IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); + mContext.registerReceiver(receiver, intentFilter); + + DownloadManager.Request requestPublic = new DownloadManager.Request(getGoodUrl()); + requestPublic.setDestinationUri(Uri.fromFile(path)); + mDownloadManager.enqueue(requestPublic); + Assert.fail("Cannot download files into other app's private directories"); + } catch (SecurityException expected) { + } finally { + mContext.unregisterReceiver(receiver); + } + } + + @Test public void testDestinationInExternalPublicDir() throws Exception { File publicLocation = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), diff --git a/tests/app/src/android/app/cts/DownloadManagerTest.java b/tests/app/src/android/app/cts/DownloadManagerTest.java index 21206871850..ae40d8989c8 100644 --- a/tests/app/src/android/app/cts/DownloadManagerTest.java +++ b/tests/app/src/android/app/cts/DownloadManagerTest.java @@ -19,6 +19,8 @@ import static android.Manifest.permission.WRITE_MEDIA_STORAGE; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static com.android.compatibility.common.util.SystemUtil.runShellCommand; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -56,6 +58,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.compatibility.common.util.CddTest; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -322,6 +325,28 @@ public class DownloadManagerTest extends DownloadManagerTestBase { } @Test + public void testSetDestinationUri_privateAppDir() throws Exception { + // Make sure the private app directory exists + runShellCommand("mkdir -p /sdcard/Android/data/com.android.shell -m 2770"); + final File path = new File("/sdcard/Android/data/com.android.shell/" + + TAG + System.currentTimeMillis()); + + final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); + try { + IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); + mContext.registerReceiver(receiver, intentFilter); + + DownloadManager.Request requestPublic = new DownloadManager.Request(getGoodUrl()); + requestPublic.setDestinationUri(Uri.fromFile(path)); + mDownloadManager.enqueue(requestPublic); + Assert.fail("Cannot download files into other app's private directories"); + } catch (SecurityException expected) { + } finally { + mContext.unregisterReceiver(receiver); + } + } + + @Test public void testSetDestinationUri_invalidRequests() throws Exception { final File documentsFile = new File( Environment.getExternalStoragePublicDirectory("TestDir"), diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java index 832127d29b3..457ebf71b69 100644 --- a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java +++ b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java @@ -706,7 +706,6 @@ public class CodecEncoderTest extends CodecEncoderTestBase { private native boolean nativeTestSetForceSyncFrame(String encoder, String file, String mime, int[] list0, int[] list1, int[] list2, int colorFormat); - @Ignore("TODO(b/) = test sometimes timesout") @LargeTest @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS) public void testSetForceSyncFrameNative() throws IOException { @@ -742,8 +741,6 @@ public class CodecEncoderTest extends CodecEncoderTestBase { mOutputBuff = new OutputManager(); mSaveToMem = true; { - /* TODO(b/147574800) */ - if (mCodecName.equals("c2.android.hevc.encoder")) return; mCodec = MediaCodec.createByCodecName(mCodecName); format.removeKey(MediaFormat.KEY_BITRATE_MODE); MediaCodecInfo.EncoderCapabilities cap = @@ -803,7 +800,6 @@ public class CodecEncoderTest extends CodecEncoderTestBase { private native boolean nativeTestAdaptiveBitRate(String encoder, String file, String mime, int[] list0, int[] list1, int[] list2, int colorFormat); - @Ignore("TODO(b/) = test sometimes timesout") @LargeTest @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS) public void testAdaptiveBitRateNative() throws IOException { @@ -811,8 +807,6 @@ public class CodecEncoderTest extends CodecEncoderTestBase { mAdaptiveBitrateMimeList.contains(mMime)); int colorFormat = -1; { - /* TODO(b/147574800) */ - if (mCodecName.equals("c2.android.hevc.encoder")) return; if (!mIsAudio) { colorFormat = findByteBufferColorFormat(mCodecName, mMime); assertTrue("no valid color formats received", colorFormat != -1); diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java index a20a8f9ee00..c56103cc4bd 100644 --- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java +++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java @@ -259,6 +259,19 @@ public class UsageStatsTest { @AppModeFull(reason = "No usage events access in instant apps") @Test + public void testLastTimeVisible_launchActivityShouldBeDetected() throws Exception { + mUiDevice.wakeUp(); + dismissKeyguard(); // also want to start out with the keyguard dismissed. + + final long startTime = System.currentTimeMillis(); + launchSubActivity(Activities.ActivityOne.class); + final long endTime = System.currentTimeMillis(); + + verifyLastTimeVisibleWithinRange(startTime, endTime, mTargetPackage); + } + + @AppModeFull(reason = "No usage events access in instant apps") + @Test public void testLastTimeAnyComponentUsed_launchActivityShouldBeDetected() throws Exception { mUiDevice.wakeUp(); dismissKeyguard(); // also want to start out with the keyguard dismissed. @@ -311,6 +324,17 @@ public class UsageStatsTest { verifyLastTimeAnyComponentUsedWithinRange(startTime, endTime, TEST_APP_PKG); } + private void verifyLastTimeVisibleWithinRange( + long startTime, long endTime, String targetPackage) { + final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats( + startTime, endTime); + final UsageStats stats = map.get(targetPackage); + assertNotNull(stats); + final long lastTimeVisible = stats.getLastTimeVisible(); + assertLessThanOrEqual(startTime, lastTimeVisible); + assertLessThanOrEqual(lastTimeVisible, endTime); + } + private void verifyLastTimeAnyComponentUsedWithinRange( long startTime, long endTime, String targetPackage) { final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats( diff --git a/tests/tests/appop/Android.bp b/tests/tests/appop/Android.bp index 33e53a58ee2..bd34b7d01f0 100644 --- a/tests/tests/appop/Android.bp +++ b/tests/tests/appop/Android.bp @@ -82,7 +82,7 @@ android_test { "libbacktrace", "libbase", "libbinder", - "libbpf", + "libbpf_bcc", "libbpf_android", "libc++", "libcgrouprc", diff --git a/tests/tests/appop/src/android/app/appops/cts/ForegroundModeTest.kt b/tests/tests/appop/src/android/app/appops/cts/ForegroundModeAndActiveTest.kt index fc635f2871d..d6e9f126272 100644 --- a/tests/tests/appop/src/android/app/appops/cts/ForegroundModeTest.kt +++ b/tests/tests/appop/src/android/app/appops/cts/ForegroundModeAndActiveTest.kt @@ -33,7 +33,9 @@ import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.ServiceConnection import android.os.IBinder +import android.os.Process import android.platform.test.annotations.AppModeFull +import android.platform.test.annotations.AsbSecurityTest import android.provider.Settings import android.provider.Settings.Global.APP_OPS_CONSTANTS import android.support.test.uiautomator.UiDevice @@ -47,12 +49,14 @@ import org.junit.Before import org.junit.Test import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit.MILLISECONDS +import java.util.concurrent.TimeoutException private const val TEST_SERVICE_PKG = "android.app.appops.cts.appthatcanbeforcedintoforegroundstates" private const val TIMEOUT_MILLIS = 45000L +private const val EXPECTED_TIMEOUT_MILLIS = 5000L @AppModeFull(reason = "This test connects to other test app") -class ForegroundModeTest { +class ForegroundModeAndActiveTest { private var previousAppOpsConstants: String? = null private val instrumentation = InstrumentationRegistry.getInstrumentation() @@ -361,6 +365,55 @@ class ForegroundModeTest { gotCallback.get(TIMEOUT_MILLIS, MILLISECONDS) } + @Test + @AsbSecurityTest(cveBugId = [208662370]) + fun activeNotChangedAfterMultipleStartsUidModeChangeAndOneStop() { + val finishCallback = CompletableFuture<Unit>() + val startCallback = CompletableFuture<Unit>() + runWithShellPermissionIdentity { + appopsManager.startWatchingActive(arrayOf(OPSTR_FINE_LOCATION), context.mainExecutor) { + op, uid, pkgName, active -> + if (pkgName == context.packageName) { + if (active) { + startCallback.complete(Unit) + } else { + finishCallback.complete(Unit) + } + } + } + } + + // Start three times + val numStarts = 3 + for (i in 1..numStarts) { + appopsManager.startOp(OPSTR_FINE_LOCATION, Process.myUid(), context.packageName, null, + null) + } + + // Wait for start + startCallback.get(TIMEOUT_MILLIS, MILLISECONDS) + withTopActivity { + // After moving to foreground, finish three times. We expect no callback until the third + for (i in 1..numStarts) { + context.getSystemService(AppOpsManager::class.java)!!.finishOp(OPSTR_FINE_LOCATION, + Process.myUid(), context.packageName, null) + val exception = try { + finishCallback.get(EXPECTED_TIMEOUT_MILLIS, MILLISECONDS) + null + } catch (e: TimeoutException) { + e + } + if (i < numStarts) { + Assert.assertNotNull("Got an active=false callback, but did not expect to", + exception) + } else { + Assert.assertNull("Expected to get an active=false callback after 3 stops", + exception) + } + } + } + } + @After fun cleanup() { foregroundControlService.cleanup() diff --git a/tests/tests/libcoreapievolution/Android.bp b/tests/tests/libcoreapievolution/Android.bp index 891ebc331ef..eed4fc3d022 100644 --- a/tests/tests/libcoreapievolution/Android.bp +++ b/tests/tests/libcoreapievolution/Android.bp @@ -30,6 +30,6 @@ android_test { test_suites: [ "cts", "general-tests", - "mts", + "mts-art", ], } diff --git a/tests/tests/libcorefileio/Android.bp b/tests/tests/libcorefileio/Android.bp index 58c388c4314..3febb32d74f 100644 --- a/tests/tests/libcorefileio/Android.bp +++ b/tests/tests/libcorefileio/Android.bp @@ -30,6 +30,6 @@ android_test { test_suites: [ "cts", "general-tests", - "mts", + "mts-art", ], } diff --git a/tests/tests/libcorelegacy22/Android.bp b/tests/tests/libcorelegacy22/Android.bp index 44a997a7e10..98684b26a93 100644 --- a/tests/tests/libcorelegacy22/Android.bp +++ b/tests/tests/libcorelegacy22/Android.bp @@ -26,6 +26,6 @@ android_test { test_suites: [ "cts", "general-tests", - "mts", + "mts-art", ], } diff --git a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java index d5d01c8f8bf..0df3b1bf447 100644 --- a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java +++ b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java @@ -162,6 +162,8 @@ abstract class MediaPlayerStressTest extends InstrumentationTestCase { } } + Preconditions.assertTestFileExists(mediaName); + File playbackOutput = new File(WorkDir.getTopDir(), "PlaybackTestResult.txt"); Writer output = new BufferedWriter(new FileWriter(playbackOutput, true)); diff --git a/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java b/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java new file mode 100644 index 00000000000..6fc2b8af79d --- /dev/null +++ b/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java @@ -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 android.mediastress.cts; + +import java.io.File; + +import junit.framework.Assert; + +/** + * Static methods used to validate preconditions in the media CTS suite + * to simplify failure diagnosis. + */ + +public final class Preconditions { + private static final String TAG = "Preconditions"; + + public static void assertTestFileExists(String pathName) { + File testFile = new File(pathName); + Assert.assertTrue("Test Setup Error, missing file: " + pathName, testFile.exists()); + } + + private Preconditions() {} +} diff --git a/tests/tests/os/src/android/os/cts/StrictModeTest.java b/tests/tests/os/src/android/os/cts/StrictModeTest.java index 0b5ed0d56f0..54caf6b47b8 100644 --- a/tests/tests/os/src/android/os/cts/StrictModeTest.java +++ b/tests/tests/os/src/android/os/cts/StrictModeTest.java @@ -77,6 +77,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -147,6 +148,63 @@ public class StrictModeTest { } @Test + public void testThreadBuilder_detectUnbufferedIo() throws Exception { + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder() + .penaltyLog() + .detectUnbufferedIo() + .build(); + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(policy).build()); + + final File test = File.createTempFile("foo", "bar"); + inspectViolation( + () -> { + writeUnbuffered(test); + }, + info -> { + assertThat(info.getViolationDetails()).isNull(); + assertThat(info.getStackTrace()).contains("UnbufferedIoViolation"); + }); + } + + @Test + public void testThreadBuilder_permitUnbufferedIo() throws Exception { + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder() + .penaltyLog() + .permitUnbufferedIo() + .build(); + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(policy).build()); + + final File test = File.createTempFile("foo", "bar"); + inspectViolation( + () -> { + writeUnbuffered(test); + }, + info -> { + assertThat(info).isNull(); + }); + } + + private void writeUnbuffered(File file) throws Exception { + if (file.exists()) { + file.delete(); + } + + try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) { + for (int i = 0; i < 11; i++) { + out.write(1); + out.write(2); + out.write(3); + out.write(4); + out.flush(); + } + } finally { + if (file.exists()) { + file.delete(); + } + } + } + + @Test public void testUnclosedCloseable() throws Exception { //clean before test System.gc(); diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml index 5ff702fb325..84470cd870d 100644 --- a/tests/tests/permission2/res/raw/android_manifest.xml +++ b/tests/tests/permission2/res/raw/android_manifest.xml @@ -5638,6 +5638,10 @@ <!-- Allows input events to be monitored. Very dangerous! @hide --> <permission android:name="android.permission.MONITOR_INPUT" android:protectionLevel="signature|recents" /> + <!-- Allows the use of FLAG_SLIPPERY, which permits touch events to slip from the current + window to the window where the touch currently is on top of. @hide --> + <permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES" + android:protectionLevel="signature|recents" /> <!-- Allows the caller to change the associations between input devices and displays. Very dangerous! @hide --> <permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY" diff --git a/tests/tests/role/Android.bp b/tests/tests/role/Android.bp index a98ea1fb756..a63856119a5 100644 --- a/tests/tests/role/Android.bp +++ b/tests/tests/role/Android.bp @@ -38,7 +38,6 @@ android_test { "cts", "general-tests", "mts-permission", - "sts", ], data: [ diff --git a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml index 90d05f99357..eb171224235 100644 --- a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml +++ b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml @@ -20,7 +20,6 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="android.app.role.cts.app"> - <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.SEND_SMS" /> <application android:label="CtsRoleTestApp"> diff --git a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java index b522f9550b9..766a92791d6 100644 --- a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java +++ b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java @@ -40,7 +40,6 @@ import android.content.pm.PermissionInfo; import android.os.Build; import android.os.Process; import android.os.UserHandle; -import android.platform.test.annotations.SecurityTest; import android.provider.Settings; import android.provider.Telephony; import android.support.test.uiautomator.By; @@ -115,9 +114,6 @@ public class RoleManagerTest { private static final String PERMISSION_MANAGE_ROLES_FROM_CONTROLLER = "com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"; - private static final String ROLE_SYSTEM_SPEECH_RECOGNIZER = - "android.app.role.SYSTEM_SPEECH_RECOGNIZER"; - private static final Instrumentation sInstrumentation = InstrumentationRegistry.getInstrumentation(); private static final Context sContext = InstrumentationRegistry.getTargetContext(); @@ -138,7 +134,8 @@ public class RoleManagerTest { @Before public void saveRoleHolder() throws Exception { - mRoleHolder = getRoleHolder(ROLE_NAME); + List<String> roleHolders = getRoleHolders(ROLE_NAME); + mRoleHolder = !roleHolders.isEmpty() ? roleHolders.get(0) : null; if (Objects.equals(mRoleHolder, APP_PACKAGE_NAME)) { removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME); @@ -914,7 +911,7 @@ public class RoleManagerTest { public void removeSmsRoleHolderThenPermissionIsRevoked() throws Exception { assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS)); - String smsRoleHolder = getRoleHolder(RoleManager.ROLE_SMS); + String smsRoleHolder = getRoleHolders(RoleManager.ROLE_SMS).get(0); addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME); addRoleHolder(RoleManager.ROLE_SMS, smsRoleHolder); @@ -928,7 +925,7 @@ public class RoleManagerTest { && sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS)); addRoleHolder(RoleManager.ROLE_DIALER, APP_PACKAGE_NAME); - String smsRoleHolder = getRoleHolder(RoleManager.ROLE_SMS); + String smsRoleHolder = getRoleHolders(RoleManager.ROLE_SMS).get(0); addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME); addRoleHolder(RoleManager.ROLE_SMS, smsRoleHolder); @@ -992,46 +989,11 @@ public class RoleManagerTest { sRoleManager.isBypassingRoleQualification())).isFalse(); } - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S") - @SecurityTest - @Test - public void systemRoleDoesNotOverrideUserRevokedPermission() throws Exception { - assumeTrue(sRoleManager.isRoleAvailable(ROLE_SYSTEM_SPEECH_RECOGNIZER)); - String systemSpeechRecognizerPackageName = getRoleHolder(ROLE_SYSTEM_SPEECH_RECOGNIZER); - if (systemSpeechRecognizerPackageName != null) { - assertThat(sPackageManager.checkPermission(android.Manifest.permission.RECORD_AUDIO, - systemSpeechRecognizerPackageName)) - .isEqualTo(PackageManager.PERMISSION_GRANTED); - } - assertThat(sPackageManager.checkPermission(android.Manifest.permission.RECORD_AUDIO, - APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_DENIED); - - runWithShellPermissionIdentity(() -> sPackageManager.updatePermissionFlags( - android.Manifest.permission.RECORD_AUDIO, APP_PACKAGE_NAME, - PackageManager.FLAG_PERMISSION_USER_SET, PackageManager.FLAG_PERMISSION_USER_SET, - Process.myUserHandle())); - runWithShellPermissionIdentity(() -> sRoleManager.setBypassingRoleQualification(true)); - try { - addRoleHolder(ROLE_SYSTEM_SPEECH_RECOGNIZER, APP_PACKAGE_NAME); - - assertThat(sPackageManager.checkPermission(android.Manifest.permission.RECORD_AUDIO, - APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_DENIED); - } finally { - runWithShellPermissionIdentity(() -> sRoleManager.setBypassingRoleQualification(false)); - } - } - @NonNull private List<String> getRoleHolders(@NonNull String roleName) throws Exception { return callWithShellPermissionIdentity(() -> sRoleManager.getRoleHolders(roleName)); } - @Nullable - private String getRoleHolder(@NonNull String roleName) throws Exception { - List<String> roleHolders = getRoleHolders(roleName); - return !roleHolders.isEmpty() ? roleHolders.get(0) : null; - } - private void assertIsRoleHolder(@NonNull String roleName, @NonNull String packageName, boolean shouldBeRoleHolder) throws Exception { List<String> packageNames = getRoleHolders(roleName); diff --git a/tests/tests/security/Android.bp b/tests/tests/security/Android.bp index 53d8f4ea4e5..5838d27729b 100644 --- a/tests/tests/security/Android.bp +++ b/tests/tests/security/Android.bp @@ -72,6 +72,9 @@ android_test { "sts", ], certificate: ":security_cts_test_certificate", + data: [ + ":RolePermissionOverrideTestApp", + ], } android_test_helper_app { @@ -84,3 +87,8 @@ android_app_certificate { name: "security_cts_test_certificate", certificate: "security_cts_test_cert", } + +android_test_helper_app { + name: "RolePermissionOverrideTestApp", + manifest: "testdata/rolepermissionoverridetestapp.xml", +} diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml index f8402ec4c11..17aa9e8f345 100644 --- a/tests/tests/security/AndroidManifest.xml +++ b/tests/tests/security/AndroidManifest.xml @@ -27,6 +27,7 @@ <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> <!-- For FileIntegrityManager --> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> diff --git a/tests/tests/security/AndroidTest.xml b/tests/tests/security/AndroidTest.xml index 6e0c8bc4f69..53a9a266b0b 100644 --- a/tests/tests/security/AndroidTest.xml +++ b/tests/tests/security/AndroidTest.xml @@ -44,6 +44,15 @@ value="pm uninstall --user 0 android.security.cts" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="mkdir -p /data/local/tmp/cts/security" /> + <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/> + </target_preparer> + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <option name="cleanup" value="true" /> + <option name="push" value="RolePermissionOverrideTestApp.apk->/data/local/tmp/cts/security/RolePermissionOverrideTestApp.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="android.security.cts" /> <option name="runtime-hint" value="1h40m18s" /> diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0922.java b/tests/tests/security/src/android/security/cts/CVE_2021_0922.java new file mode 100644 index 00000000000..855ad37788b --- /dev/null +++ b/tests/tests/security/src/android/security/cts/CVE_2021_0922.java @@ -0,0 +1,70 @@ +/* + * 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 static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeNoException; + +import android.app.Instrumentation; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.platform.test.annotations.AsbSecurityTest; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class CVE_2021_0922 { + + private Instrumentation mInstrumentation; + + @Before + public void setUp() { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + } + + /** + * b/195630721 + */ + @AsbSecurityTest(cveBugId = 195630721) + @Test + public void testPocCVE_2021_0922() throws Exception { + String packageName = "com.android.managedprovisioning"; + try { + PackageInfo packageInfo = mInstrumentation.getContext().getPackageManager() + .getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); + boolean isPermissionPresent = false; + for (int i = 0; i < packageInfo.requestedPermissions.length; ++i) { + if ((packageInfo.requestedPermissionsFlags[i] + & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) { + String permission = packageInfo.requestedPermissions[i]; + if (permission.equals(android.Manifest.permission.MANAGE_APP_OPS_MODES)) { + isPermissionPresent = true; + break; + } + } + } + assertTrue(isPermissionPresent); + } catch (PackageManager.NameNotFoundException e) { + assumeNoException(e); + } + } +} diff --git a/tests/tests/security/src/android/security/cts/RolePermissionOverrideTest.kt b/tests/tests/security/src/android/security/cts/RolePermissionOverrideTest.kt new file mode 100644 index 00000000000..2394cd22ffc --- /dev/null +++ b/tests/tests/security/src/android/security/cts/RolePermissionOverrideTest.kt @@ -0,0 +1,153 @@ +/* + * 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.app.role.RoleManager +import android.content.pm.PackageManager +import android.os.Process +import android.platform.test.annotations.AsbSecurityTest +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity +import com.android.compatibility.common.util.SystemUtil.runShellCommand +import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity +import com.android.compatibility.common.util.mainline.MainlineModule +import com.android.compatibility.common.util.mainline.ModuleDetector +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit +import java.util.function.Consumer +import org.junit.After +import org.junit.Assume.assumeFalse +import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RolePermissionOverrideTest { + private val instrumentation = InstrumentationRegistry.getInstrumentation() + private val context = instrumentation.targetContext + private val packageManager = context.packageManager + private val roleManager = context.getSystemService(RoleManager::class.java) + private val user = Process.myUserHandle() + + @Before + fun setUp() { + installPackage(TEST_APP_APK_PATH) + } + + @After + fun tearDown() { + uninstallPackage(TEST_APP_PACKAGE_NAME) + } + + @AsbSecurityTest(cveBugId = [202312327]) + @Test + fun systemRoleDoesNotOverrideUserRevokedPermission() { + assumeFalse( + ModuleDetector.moduleIsPlayManaged( + packageManager, MainlineModule.PERMISSION_CONTROLLER_APEX + ) + ) + assumeTrue(roleManager.isRoleAvailable(ROLE_SYSTEM_SPEECH_RECOGNIZER)) + val systemSpeechRecognizerPackageName = getRoleHolder(ROLE_SYSTEM_SPEECH_RECOGNIZER) + if (systemSpeechRecognizerPackageName != null) { + assertPermissionState( + systemSpeechRecognizerPackageName, android.Manifest.permission.RECORD_AUDIO, true + ) + } + assertPermissionState( + TEST_APP_PACKAGE_NAME, android.Manifest.permission.RECORD_AUDIO, false + ) + + runWithShellPermissionIdentity { + packageManager.updatePermissionFlags( + android.Manifest.permission.RECORD_AUDIO, TEST_APP_PACKAGE_NAME, + PackageManager.FLAG_PERMISSION_USER_SET, PackageManager.FLAG_PERMISSION_USER_SET, + user + ) + } + setBypassingRoleQualification(true) + try { + addRoleHolder(ROLE_SYSTEM_SPEECH_RECOGNIZER, TEST_APP_PACKAGE_NAME) + + assertPermissionState( + TEST_APP_PACKAGE_NAME, android.Manifest.permission.RECORD_AUDIO, false + ) + } finally { + setBypassingRoleQualification(false) + } + } + + private fun installPackage(apkPath: String) { + runShellCommand("pm install -r --user ${user.identifier} $apkPath") + } + + private fun uninstallPackage(packageName: String) { + runShellCommand("pm uninstall -r --user ${user.identifier} $packageName") + } + + private fun getRoleHolders(roleName: String): List<String> = + callWithShellPermissionIdentity { roleManager.getRoleHolders(roleName) } + + private fun getRoleHolder(roleName: String): String? = getRoleHolders(roleName).firstOrNull() + + private fun setBypassingRoleQualification(bypassRoleQualification: Boolean) { + runWithShellPermissionIdentity { + roleManager.setBypassingRoleQualification(bypassRoleQualification) + } + } + + private fun addRoleHolder(roleName: String, packageName: String) { + val future = CallbackFuture() + runWithShellPermissionIdentity { + roleManager.addRoleHolderAsUser( + roleName, packageName, 0, user, context.mainExecutor, future + ) + } + assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isEqualTo(true) + } + + private fun assertPermissionState( + packageName: String, + permissionName: String, + isGranted: Boolean + ) { + assertThat(packageManager.checkPermission(permissionName, packageName)).isEqualTo( + if (isGranted) PackageManager.PERMISSION_GRANTED else PackageManager.PERMISSION_DENIED + ) + } + + companion object { + private const val TEST_APP_APK_PATH = + "/data/local/tmp/cts/security/RolePermissionOverrideTestApp.apk" + private const val TEST_APP_PACKAGE_NAME = + "android.security.cts.rolepermissionoverridetestapp" + + private const val ROLE_SYSTEM_SPEECH_RECOGNIZER = + "android.app.role.SYSTEM_SPEECH_RECOGNIZER" + + private const val TIMEOUT_MILLIS = 15_000L + } + + private class CallbackFuture : CompletableFuture<Boolean>(), Consumer<Boolean> { + override fun accept(successful: Boolean) { + complete(successful) + } + } +} diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java index 76b6549c0d9..ddecbc824c2 100644 --- a/tests/tests/security/src/android/security/cts/StagefrightTest.java +++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java @@ -1151,7 +1151,7 @@ public class StagefrightTest { } @Test - @AsbSecurityTest(cveBugId = 110435401) + @AsbSecurityTest(cveBugId = 68664359) public void testStagefright_bug_110435401() throws Exception { doStagefrightTest(R.raw.bug_110435401, 60000); } diff --git a/tests/tests/security/testdata/rolepermissionoverridetestapp.xml b/tests/tests/security/testdata/rolepermissionoverridetestapp.xml new file mode 100644 index 00000000000..783382abdf3 --- /dev/null +++ b/tests/tests/security/testdata/rolepermissionoverridetestapp.xml @@ -0,0 +1,26 @@ +<?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.security.cts.rolepermissionoverridetestapp"> + + <uses-permission android:name="android.permission.RECORD_AUDIO" /> + + <application /> +</manifest> |