Unit tests for PermissionUtils

Test: atest MediaProviderTests:PermissionUtilsTest

Bug: 157716052
Merged-In: I2e0b9c991c0f6b9789933d9bebb81c2b86f6f4f6
Merged-In: I612f6e68eb933484a45571013a3585c5635dae72
Change-Id: I612f6e68eb933484a45571013a3585c5635dae72
(cherry picked from commit 9f752e967da6122b31e069d712c7c29169a3f05a)
diff --git a/tests/Android.bp b/tests/Android.bp
index cdf9280..5a354a8 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -1,3 +1,55 @@
+android_test_helper_app {
+    name: "MediaProviderTestAppWithStoragePerms",
+    manifest: "test_app/TestAppWithStoragePerms.xml",
+    srcs: [
+        "test_app/src/**/*.java",
+        "src/com/android/providers/media/util/TestUtils.java",
+    ],
+    static_libs: [
+        "cts-install-lib",
+    ],
+    sdk_version: "test_current",
+    test_suites: [
+        "device-tests",
+        "mts-mediaprovider",
+    ],
+}
+
+android_test_helper_app {
+    name: "MediaProviderTestAppWithoutPerms",
+    manifest: "test_app/TestAppWithoutPerms.xml",
+    srcs: [
+        "test_app/src/**/*.java",
+        "src/com/android/providers/media/util/TestUtils.java",
+    ],
+    static_libs: [
+        "cts-install-lib",
+    ],
+    sdk_version: "test_current",
+    test_suites: [
+        "device-tests",
+        "mts-mediaprovider",
+    ],
+}
+
+android_test_helper_app {
+    name: "LegacyMediaProviderTestApp",
+    manifest: "test_app/LegacyTestApp.xml",
+    srcs: [
+        "test_app/src/**/*.java",
+        "src/com/android/providers/media/util/TestUtils.java",
+    ],
+    static_libs: [
+        "cts-install-lib",
+    ],
+    sdk_version: "test_current",
+    target_sdk_version: "28",
+    test_suites: [
+        "device-tests",
+        "mts-mediaprovider",
+    ],
+}
+
 // This looks a bit awkward, but we need our tests to run against either
 // MediaProvider or MediaProviderGoogle, and we don't know which one is
 // on the device being tested, so we can't sign our tests with a key that
@@ -49,6 +101,7 @@
         "mockito-target",
         "modules-utils-build",
         "truth-prebuilt",
+        "cts-install-lib",
     ],
 
     certificate: "media",
@@ -61,6 +114,12 @@
             "-Xep:MissingFail:ERROR",
         ],
     },
+
+    java_resources: [
+        ":MediaProviderTestAppWithStoragePerms",
+        ":MediaProviderTestAppWithoutPerms",
+        ":LegacyMediaProviderTestApp",
+    ],
 }
 
 filegroup {
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index b5c556a..48bb6aa 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -4,7 +4,7 @@
 
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
+    <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
     <application android:label="MediaProvider Tests">
         <uses-library android:name="android.test.runner" />
 
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
index fe7de6a..6b929cf 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -16,6 +16,9 @@
 <configuration description="Runs Tests for MediaProvder.">
     <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
         <option name="test-file-name" value="MediaProviderTests.apk" />
+        <option name="test-file-name" value="MediaProviderTestAppWithStoragePerms.apk" />
+        <option name="test-file-name" value="MediaProviderTestAppWithoutPerms.apk" />
+        <option name="test-file-name" value="LegacyMediaProviderTestApp.apk" />
         <option name="install-arg" value="-g" />
     </target_preparer>
 
diff --git a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
index 1eba379..1fc43cb 100644
--- a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
@@ -16,8 +16,25 @@
 
 package com.android.providers.media.util;
 
+import static android.Manifest.permission.MANAGE_APP_OPS_MODES;
+import static android.Manifest.permission.UPDATE_APP_OPS_STATS;
+import static android.app.AppOpsManager.OPSTR_NO_ISOLATED_STORAGE;
+import static android.app.AppOpsManager.OPSTR_READ_MEDIA_AUDIO;
+import static android.app.AppOpsManager.OPSTR_READ_MEDIA_IMAGES;
+import static android.app.AppOpsManager.OPSTR_READ_MEDIA_VIDEO;
+import static android.app.AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES;
+import static android.app.AppOpsManager.OPSTR_WRITE_MEDIA_AUDIO;
+import static android.app.AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES;
+import static android.app.AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.android.providers.media.util.PermissionUtils.checkAppOpRequestInstallPackagesForSharedUid;
+import static com.android.providers.media.util.PermissionUtils.checkIsLegacyStorageGranted;
 import static com.android.providers.media.util.PermissionUtils.checkNoIsolatedStorageGranted;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionAccessMtp;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionDelegator;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionInstallPackages;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionManager;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadAudio;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadImages;
@@ -29,20 +46,53 @@
 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteImages;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteStorage;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteVideo;
+import static com.android.providers.media.util.PermissionUtils.checkWriteImagesOrVideoAppOps;
+import static com.android.providers.media.util.TestUtils.QUERY_TYPE;
+import static com.android.providers.media.util.TestUtils.RUN_INFINITE_ACTIVITY;
+import static com.android.providers.media.util.TestUtils.adoptShellPermission;
+import static com.android.providers.media.util.TestUtils.dropShellPermission;
+import static com.android.providers.media.util.TestUtils.getPid;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+
+import android.app.AppOpsManager;
 import android.content.Context;
+import android.content.Intent;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashMap;
+import java.util.Map;
+
 @RunWith(AndroidJUnit4.class)
 public class PermissionUtilsTest {
+    private static final TestApp TEST_APP_WITH_STORAGE_PERMS = new TestApp(
+            "TestAppWithStoragePerms",
+            "com.android.providers.media.testapp.withstorageperms", 1, false,
+            "MediaProviderTestAppWithStoragePerms.apk");
+    private static final TestApp TEST_APP_WITHOUT_PERMS = new TestApp("TestAppWithoutPerms",
+            "com.android.providers.media.testapp.withoutperms", 1, false,
+            "MediaProviderTestAppWithoutPerms.apk");
+    private static final TestApp LEGACY_TEST_APP = new TestApp("LegacyTestApp",
+            "com.android.providers.media.testapp.legacy", 1, false,
+            "LegacyMediaProviderTestApp.apk");
+    private static final Map<String, Integer> pidMap = new HashMap<>();
+
+    @BeforeClass
+    public static void startTestApps() throws Exception {
+        startTestApp(TEST_APP_WITH_STORAGE_PERMS);
+        startTestApp(TEST_APP_WITHOUT_PERMS);
+        startTestApp(LEGACY_TEST_APP);
+    }
+
     @Test
     public void testConstructor() {
         new PermissionUtils();
@@ -53,26 +103,28 @@
      * we expect to be holding.
      */
     @Test
-    public void testSelfPermissions() throws Exception {
-        final Context context = InstrumentationRegistry.getContext();
+    public void testSelfPermissions() {
+        final Context context = getContext();
         final int pid = android.os.Process.myPid();
         final int uid = android.os.Process.myUid();
         final String packageName = context.getPackageName();
 
-        assertTrue(checkPermissionSelf(context, pid, uid));
-        assertFalse(checkPermissionShell(context, pid, uid));
-        assertFalse(checkPermissionManager(context, pid, uid, packageName, null));
-        assertFalse(checkPermissionDelegator(context, pid, uid));
+        assertThat(checkPermissionSelf(context, pid, uid)).isTrue();
+        assertThat(checkPermissionShell(context, pid, uid)).isFalse();
+        assertThat(checkPermissionManager(context, pid, uid, packageName, null)).isFalse();
+        assertThat(checkPermissionDelegator(context, pid, uid)).isFalse();
 
-        assertTrue(checkPermissionReadStorage(context, pid, uid, packageName, null));
-        assertTrue(checkPermissionWriteStorage(context, pid, uid, packageName, null));
+        assertThat(checkPermissionReadStorage(context, pid, uid, packageName, null)).isTrue();
+        assertThat(checkPermissionWriteStorage(context, pid, uid, packageName, null)).isTrue();
 
-        assertTrue(checkPermissionReadAudio(context, pid, uid, packageName, null));
-        assertFalse(checkPermissionWriteAudio(context, pid, uid, packageName, null));
-        assertTrue(checkPermissionReadVideo(context, pid, uid, packageName, null));
-        assertFalse(checkPermissionWriteVideo(context, pid, uid, packageName, null));
-        assertTrue(checkPermissionReadImages(context, pid, uid, packageName, null));
-        assertFalse(checkPermissionWriteImages(context, pid, uid, packageName, null));
+        assertThat(checkPermissionReadAudio(context, pid, uid, packageName, null)).isTrue();
+        assertThat(checkPermissionWriteAudio(context, pid, uid, packageName, null)).isFalse();
+        assertThat(checkPermissionReadVideo(context, pid, uid, packageName, null)).isTrue();
+        assertThat(checkPermissionWriteVideo(context, pid, uid, packageName, null)).isFalse();
+        assertThat(checkPermissionReadImages(context, pid, uid, packageName, null)).isTrue();
+        assertThat(checkPermissionWriteImages(context, pid, uid, packageName, null)).isFalse();
+        assertThat(checkPermissionInstallPackages(getContext(), pid, uid,
+                getContext().getPackageName(), null)).isFalse();
     }
 
     /**
@@ -80,9 +132,328 @@
      */
     @Test
     public void testNoIsolatedStorageIsByDefaultDenied() throws Exception {
-        final Context context = InstrumentationRegistry.getContext();
+        final Context context = getContext();
         final int uid = android.os.Process.myUid();
         final String packageName = context.getPackageName();
-        assertFalse(checkNoIsolatedStorageGranted(context, uid, packageName, null));
+        assertThat(checkNoIsolatedStorageGranted(context, uid, packageName, null)).isFalse();
+    }
+
+    @Test
+    public void testDefaultPermissionsOnTestAppWithStoragePerms() throws Exception {
+        String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
+        int testAppUid = getContext().getPackageManager().getPackageUid(packageName, 0);
+        int testAppPid = pidMap.get(packageName);
+        adoptShellPermission(UPDATE_APP_OPS_STATS);
+
+        try {
+            assertThat(checkPermissionSelf(getContext(), testAppPid, testAppUid)).isFalse();
+            assertThat(checkPermissionShell(getContext(), testAppPid, testAppUid)).isFalse();
+            assertThat(
+                    checkPermissionManager(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isFalse();
+            assertThat(
+                    checkIsLegacyStorageGranted(getContext(), testAppUid, packageName,
+                            null)).isFalse();
+            assertThat(
+                    checkPermissionInstallPackages(getContext(), testAppPid, testAppUid,
+                            packageName, null)).isFalse();
+            assertThat(
+                    checkPermissionAccessMtp(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isFalse();
+            assertThat(
+                    checkPermissionWriteStorage(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isTrue();
+            checkReadPermissions(testAppPid, testAppUid, packageName, true);
+        } finally {
+            dropShellPermission();
+        }
+    }
+
+    @Test
+    public void testDefaultPermissionsOnTestAppWithoutPerms() throws Exception {
+        String packageName = TEST_APP_WITHOUT_PERMS.getPackageName();
+        int testAppUid = getContext().getPackageManager().getPackageUid(packageName, 0);
+        int testAppPid = pidMap.get(packageName);
+        adoptShellPermission(UPDATE_APP_OPS_STATS);
+
+        try {
+            assertThat(checkPermissionSelf(getContext(), testAppPid, testAppUid)).isFalse();
+            assertThat(checkPermissionShell(getContext(), testAppPid, testAppUid)).isFalse();
+            assertThat(
+                    checkPermissionManager(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isFalse();
+            assertThat(
+                    checkIsLegacyStorageGranted(getContext(), testAppUid, packageName,
+                            null)).isFalse();
+            assertThat(
+                    checkPermissionInstallPackages(getContext(), testAppPid, testAppUid,
+                            packageName,
+                            null)).isFalse();
+            assertThat(
+                    checkPermissionAccessMtp(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isFalse();
+            assertThat(
+                    checkPermissionWriteStorage(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isFalse();
+            checkReadPermissions(testAppPid, testAppUid, packageName, false);
+        } finally {
+            dropShellPermission();
+        }
+    }
+
+    @Test
+    public void testDefaultPermissionsOnLegacyTestApp() throws Exception {
+        String packageName = LEGACY_TEST_APP.getPackageName();
+        int testAppUid = getContext().getPackageManager().getPackageUid(packageName, 0);
+        int testAppPid = pidMap.get(packageName);
+        adoptShellPermission(UPDATE_APP_OPS_STATS);
+
+        try {
+            assertThat(checkPermissionSelf(getContext(), testAppPid, testAppUid)).isFalse();
+            assertThat(checkPermissionShell(getContext(), testAppPid, testAppUid)).isFalse();
+            assertThat(
+                    checkPermissionManager(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isFalse();
+            assertThat(
+                    checkIsLegacyStorageGranted(getContext(), testAppUid, packageName,
+                            null)).isTrue();
+            assertThat(
+                    checkPermissionInstallPackages(getContext(), testAppPid, testAppUid,
+                            packageName, null)).isFalse();
+            assertThat(
+                    checkPermissionAccessMtp(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isFalse();
+            assertThat(
+                    checkPermissionWriteStorage(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isFalse();
+            checkReadPermissions(testAppPid, testAppUid, packageName, true);
+        } finally {
+            dropShellPermission();
+        }
+    }
+
+
+    @Test
+    public void testSystemGalleryPermissionsOnTestApp() throws Exception {
+        String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
+        int testAppUid = getContext().getPackageManager().getPackageUid(packageName, 0);
+        int testAppPid = pidMap.get(packageName);
+        adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
+
+        try {
+            checkPermissionsForGallery(testAppUid, testAppPid, packageName, false);
+
+            final String[] SYSTEM_GALERY_APPOPS =
+                    {OPSTR_WRITE_MEDIA_IMAGES, OPSTR_WRITE_MEDIA_VIDEO};
+            for (String op : SYSTEM_GALERY_APPOPS) {
+                modifyAppOp(testAppUid, op, AppOpsManager.MODE_ALLOWED);
+            }
+            checkPermissionsForGallery(testAppUid, testAppPid, packageName, true);
+
+            for (String op : SYSTEM_GALERY_APPOPS) {
+                modifyAppOp(testAppUid, op, AppOpsManager.MODE_ERRORED);
+            }
+            checkPermissionsForGallery(testAppUid, testAppPid, packageName, false);
+        } finally {
+            dropShellPermission();
+        }
+    }
+
+    @Test
+    public void testIsolatedStoragePermissionsOnTestApp() throws Exception {
+        String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
+        int testAppUid = getContext().getPackageManager().getPackageUid(packageName, 0);
+        adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
+
+        try {
+            assertThat(
+                    checkNoIsolatedStorageGranted(getContext(), testAppUid, packageName,
+                            null)).isFalse();
+
+            modifyAppOp(testAppUid, OPSTR_NO_ISOLATED_STORAGE, AppOpsManager.MODE_ALLOWED);
+            assertThat(
+                    checkNoIsolatedStorageGranted(getContext(), testAppUid, packageName,
+                            null)).isTrue();
+
+            modifyAppOp(testAppUid, OPSTR_NO_ISOLATED_STORAGE, AppOpsManager.MODE_ERRORED);
+            assertThat(
+                    checkNoIsolatedStorageGranted(getContext(), testAppUid, packageName,
+                            null)).isFalse();
+        } finally {
+            dropShellPermission();
+        }
+    }
+
+    @Test
+    public void testReadVideoOnTestApp() throws Exception {
+        final String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
+        int testAppUid = getContext().getPackageManager().getPackageUid(
+                packageName, 0);
+        int testAppPid = pidMap.get(packageName);
+        adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
+
+        try {
+            assertThat(
+                    checkPermissionReadVideo(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isTrue();
+
+            modifyAppOp(testAppUid, OPSTR_READ_MEDIA_VIDEO, AppOpsManager.MODE_ERRORED);
+            assertThat(
+                    checkPermissionReadVideo(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isFalse();
+
+            modifyAppOp(testAppUid, OPSTR_READ_MEDIA_VIDEO, AppOpsManager.MODE_ALLOWED);
+            assertThat(
+                    checkPermissionReadVideo(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isTrue();
+        } finally {
+            dropShellPermission();
+        }
+    }
+
+    @Test
+    public void testWriteAudioOnTestApp() throws Exception {
+        final String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
+        int testAppUid = getContext().getPackageManager().getPackageUid(
+                packageName, 0);
+        int testAppPid = pidMap.get(packageName);
+        adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
+
+        try {
+            assertThat(
+                    checkPermissionWriteAudio(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isFalse();
+
+            modifyAppOp(testAppUid, OPSTR_WRITE_MEDIA_AUDIO, AppOpsManager.MODE_ALLOWED);
+            assertThat(
+                    checkPermissionWriteAudio(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isTrue();
+
+            modifyAppOp(testAppUid, OPSTR_WRITE_MEDIA_AUDIO, AppOpsManager.MODE_ERRORED);
+            assertThat(
+                    checkPermissionWriteAudio(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isFalse();
+        } finally {
+            dropShellPermission();
+        }
+    }
+
+    @Test
+    public void testReadAudioOnTestApp() throws Exception {
+        final String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
+        int testAppUid = getContext().getPackageManager().getPackageUid(
+                packageName, 0);
+        int testAppPid = pidMap.get(packageName);
+        adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
+
+        try {
+            assertThat(
+                    checkPermissionReadAudio(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isTrue();
+
+            modifyAppOp(testAppUid, OPSTR_READ_MEDIA_AUDIO, AppOpsManager.MODE_ERRORED);
+            assertThat(
+                    checkPermissionReadAudio(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isFalse();
+
+            modifyAppOp(testAppUid, OPSTR_READ_MEDIA_AUDIO, AppOpsManager.MODE_ALLOWED);
+            assertThat(
+                    checkPermissionReadAudio(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isTrue();
+        } finally {
+            dropShellPermission();
+        }
+    }
+
+    @Test
+    public void testReadImagesOnTestApp() throws Exception {
+        final String packageName = TEST_APP_WITH_STORAGE_PERMS.getPackageName();
+        int testAppUid = getContext().getPackageManager().getPackageUid(packageName, 0);
+        int testAppPid = pidMap.get(packageName);
+        adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
+
+        try {
+            assertThat(
+                    checkPermissionReadImages(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isTrue();
+
+            modifyAppOp(testAppUid, OPSTR_READ_MEDIA_IMAGES, AppOpsManager.MODE_ERRORED);
+            assertThat(
+                    checkPermissionReadImages(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isFalse();
+
+            modifyAppOp(testAppUid, OPSTR_READ_MEDIA_IMAGES, AppOpsManager.MODE_ALLOWED);
+            assertThat(
+                    checkPermissionReadImages(getContext(), testAppPid, testAppUid, packageName,
+                            null)).isTrue();
+        } finally {
+            dropShellPermission();
+        }
+    }
+
+    @Test
+    public void testOpstrInstallPermissionsOnTestApp() throws Exception {
+        int testAppUid = getContext().getPackageManager().getPackageUid(
+                TEST_APP_WITH_STORAGE_PERMS.getPackageName(), 0);
+        String[] packageName = {TEST_APP_WITH_STORAGE_PERMS.getPackageName()};
+        adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
+
+        try {
+            assertThat(
+                    checkAppOpRequestInstallPackagesForSharedUid(getContext(), testAppUid,
+                            packageName, null)).isFalse();
+
+            modifyAppOp(testAppUid, OPSTR_REQUEST_INSTALL_PACKAGES, AppOpsManager.MODE_ALLOWED);
+            assertThat(
+                    checkAppOpRequestInstallPackagesForSharedUid(getContext(), testAppUid,
+                            packageName, null)).isTrue();
+
+            modifyAppOp(testAppUid, OPSTR_REQUEST_INSTALL_PACKAGES, AppOpsManager.MODE_ERRORED);
+            assertThat(
+                    checkAppOpRequestInstallPackagesForSharedUid(getContext(), testAppUid,
+                            packageName, null)).isFalse();
+        } finally {
+            // App gets killed when we try to give it OPSTR_REQUEST_INSTALL_PACKAGES. Restart the
+            // app so it doesn't affect other tests.
+            startTestApp(TEST_APP_WITH_STORAGE_PERMS);
+            dropShellPermission();
+        }
+    }
+
+    static private void startTestApp(TestApp testApp) throws Exception {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setPackage(testApp.getPackageName());
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        intent.putExtra(QUERY_TYPE, RUN_INFINITE_ACTIVITY);
+        getContext().startActivity(intent);
+        pidMap.put(testApp.getPackageName(), getPid(testApp.getPackageName()));
+    }
+
+    static private void modifyAppOp(int uid, String op, int mode) {
+        getContext().getSystemService(AppOpsManager.class).setUidMode(op, uid, mode);
+    }
+
+    static private void checkPermissionsForGallery(int uid, int pid, String packageName,
+            boolean expected) {
+        assertEquals(expected,
+                checkWriteImagesOrVideoAppOps(getContext(), uid, packageName, null));
+        assertEquals(expected,
+                checkPermissionWriteImages(getContext(), pid, uid, packageName, null));
+        assertEquals(expected,
+                checkPermissionWriteVideo(getContext(), pid, uid, packageName, null));
+        assertThat(checkPermissionWriteAudio(getContext(), pid, uid, packageName, null)).isFalse();
+    }
+
+    static private void checkReadPermissions(int pid, int uid, String packageName,
+            boolean expected) {
+        assertEquals(expected,
+                checkPermissionReadStorage(getContext(), pid, uid, packageName, null));
+        assertEquals(expected,
+                checkPermissionReadAudio(getContext(), pid, uid, packageName, null));
+        assertEquals(expected,
+                checkPermissionReadImages(getContext(), pid, uid, packageName, null));
+        assertEquals(expected,
+                checkPermissionReadVideo(getContext(), pid, uid, packageName, null));
     }
 }
diff --git a/tests/src/com/android/providers/media/util/TestUtils.java b/tests/src/com/android/providers/media/util/TestUtils.java
new file mode 100644
index 0000000..1581b03
--- /dev/null
+++ b/tests/src/com/android/providers/media/util/TestUtils.java
@@ -0,0 +1,58 @@
+/*
+ * 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 com.android.providers.media.util;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+class TestUtils {
+    public static final String QUERY_TYPE = "com.android.providers.media.util.QUERY_TYPE";
+    public static final String RUN_INFINITE_ACTIVITY =
+            "com.android.providers.media.util.RUN_INFINITE_ACTIVITY";
+
+    private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20);
+    private static final long POLLING_SLEEP_MILLIS = 100;
+
+    public static void adoptShellPermission(@NonNull String... perms) {
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(perms);
+    }
+
+    public static void dropShellPermission() {
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+    }
+
+    public static int getPid(String packageName)
+            throws IOException, InterruptedException, TimeoutException {
+        UiDevice uiDevice = UiDevice.getInstance(getInstrumentation());
+        for (int i = 0; i < POLLING_TIMEOUT_MILLIS / POLLING_SLEEP_MILLIS; i++) {
+            String pid = uiDevice.executeShellCommand("pidof " + packageName).trim();
+            if (pid.length() > 0) {
+                return new Integer(pid);
+            }
+            Thread.sleep(POLLING_SLEEP_MILLIS);
+        }
+
+        throw new TimeoutException("Timed out waiting for pid");
+    }
+}
diff --git a/tests/test_app/LegacyTestApp.xml b/tests/test_app/LegacyTestApp.xml
new file mode 100644
index 0000000..f73143b
--- /dev/null
+++ b/tests/test_app/LegacyTestApp.xml
@@ -0,0 +1,36 @@
+<?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="com.android.providers.media.testapp.legacy"
+          android:versionCode="1"
+          android:versionName="1.0">
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <application android:label="LegacyTestApp"
+                 android:requestLegacyExternalStorage="true">
+        <activity android:name="com.android.providers.media.util.TestAppActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/test_app/TestAppWithStoragePerms.xml b/tests/test_app/TestAppWithStoragePerms.xml
new file mode 100644
index 0000000..694e17f
--- /dev/null
+++ b/tests/test_app/TestAppWithStoragePerms.xml
@@ -0,0 +1,36 @@
+<?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="com.android.providers.media.testapp.withstorageperms"
+          android:versionCode="1"
+          android:versionName="1.0">
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+
+    <application android:label="TestAppPerms">
+        <activity android:name="com.android.providers.media.util.TestAppActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/test_app/TestAppWithoutPerms.xml b/tests/test_app/TestAppWithoutPerms.xml
new file mode 100644
index 0000000..875d2c0
--- /dev/null
+++ b/tests/test_app/TestAppWithoutPerms.xml
@@ -0,0 +1,33 @@
+<?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="com.android.providers.media.testapp.withoutperms"
+          android:versionCode="1"
+          android:versionName="1.0">
+
+    <application android:label="TestAppWithoutPerms">
+        <activity android:name="com.android.providers.media.util.TestAppActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/test_app/src/com/android/providers/media/util/TestAppActivity.java b/tests/test_app/src/com/android/providers/media/util/TestAppActivity.java
new file mode 100644
index 0000000..8c95cce
--- /dev/null
+++ b/tests/test_app/src/com/android/providers/media/util/TestAppActivity.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.providers.media.util;
+
+import static com.android.providers.media.util.TestUtils.QUERY_TYPE;
+import static com.android.providers.media.util.TestUtils.RUN_INFINITE_ACTIVITY;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+
+public class TestAppActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        String queryType = getIntent().getStringExtra(QUERY_TYPE);
+        queryType = queryType == null ? "null" : queryType;
+
+        switch (queryType) {
+            case RUN_INFINITE_ACTIVITY:
+                while (true) {
+                }
+            default:
+                throw new IllegalStateException(
+                        "Unknown query received from launcher app: " + queryType);
+        }
+    }
+}