Test restricted permission vs shared uid

- esp. read-external-storage perm
- the rest behaves the same

Bug: 136129296
Test: atest RestrictedStoragePermissionSharedUidTest RestrictedPermissionsTest
Change-Id: I178d8ab6b09a518f8f9149fbe66b54fe0324a0dd
(cherry picked from commit 5bcbdeb1739b6f4cad7e9e303c04dafaa1fa09a3)
diff --git a/tests/tests/permission2/AndroidTest.xml b/tests/tests/permission2/AndroidTest.xml
index 9e78d64..bcab153 100644
--- a/tests/tests/permission2/AndroidTest.xml
+++ b/tests/tests/permission2/AndroidTest.xml
@@ -39,6 +39,12 @@
         <option name="push" value="CtsStoragePermissionsUserOptInSdk22.apk->/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserOptInSdk22.apk" />
         <option name="push" value="CtsStoragePermissionsUserOptInSdk28.apk->/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserOptInSdk28.apk" />
         <option name="push" value="CtsStoragePermissionsUserOptOutSdk29.apk->/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserOptOutSdk29.apk" />
+        <option name="push" value="CtsLegacyStorageNotIsolatedWithSharedUid.apk->/data/local/tmp/cts/permissions2/CtsLegacyStorageNotIsolatedWithSharedUid.apk" />
+        <option name="push" value="CtsLegacyStorageIsolatedWithSharedUid.apk->/data/local/tmp/cts/permissions2/CtsLegacyStorageIsolatedWithSharedUid.apk" />
+        <option name="push" value="CtsLegacyStorageRestrictedWithSharedUid.apk->/data/local/tmp/cts/permissions2/CtsLegacyStorageRestrictedWithSharedUid.apk" />
+        <option name="push" value="CtsLegacyStorageRestrictedSdk28WithSharedUid.apk->/data/local/tmp/cts/permissions2/CtsLegacyStorageRestrictedSdk28WithSharedUid.apk" />
+        <option name="push" value="CtsSMSRestrictedWithSharedUid.apk->/data/local/tmp/cts/permissions2/CtsSMSRestrictedWithSharedUid.apk" />
+        <option name="push" value="CtsSMSNotRestrictedWithSharedUid.apk->/data/local/tmp/cts/permissions2/CtsSMSNotRestrictedWithSharedUid.apk" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp
new file mode 100644
index 0000000..6ee6120
--- /dev/null
+++ b/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+    name: "CtsLegacyStorageIsolatedWithSharedUid",
+    defaults: ["cts_defaults"],
+
+    sdk_version: "current",
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+        "cts_instant",
+    ]
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/AndroidManifest.xml b/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/AndroidManifest.xml
new file mode 100644
index 0000000..9054889
--- /dev/null
+++ b/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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.permission2.cts.legacystoragewithshareduid.isolated"
+    android:versionCode="1"
+    android:sharedUserId="android.permission2.cts.restrictedpermissionuser.shareduid">
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <application android:label="CtsLegacyStorageIsolatedWithSharedUid" />
+</manifest>
diff --git a/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp
new file mode 100644
index 0000000..63c2a14
--- /dev/null
+++ b/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+    name: "CtsLegacyStorageNotIsolatedWithSharedUid",
+    defaults: ["cts_defaults"],
+
+    sdk_version: "current",
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+        "cts_instant",
+    ]
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/AndroidManifest.xml b/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/AndroidManifest.xml
new file mode 100644
index 0000000..179f19a
--- /dev/null
+++ b/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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.permission2.cts.legacystoragewithshareduid.notisolated"
+    android:versionCode="1"
+    android:sharedUserId="android.permission2.cts.restrictedpermissionuser.shareduid">
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <application android:label="CtsLegacyStorageNotIsolatedWithSharedUid"
+         android:requestLegacyExternalStorage="true" />
+</manifest>
diff --git a/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp
new file mode 100644
index 0000000..50ac715
--- /dev/null
+++ b/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+    name: "CtsLegacyStorageRestrictedSdk28WithSharedUid",
+    defaults: ["cts_defaults"],
+
+    sdk_version: "current",
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+        "cts_instant",
+    ]
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/AndroidManifest.xml b/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/AndroidManifest.xml
new file mode 100644
index 0000000..89cadcd
--- /dev/null
+++ b/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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.permission2.cts.legacystoragewithshareduid.restrictedsdk28"
+    android:versionCode="1"
+    android:sharedUserId="android.permission2.cts.restrictedpermissionuser.shareduid">
+
+    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <application android:label="CtsLegacyStorageRestrictedSdk28WithSharedUid" />
+</manifest>
diff --git a/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp
new file mode 100644
index 0000000..78068f9
--- /dev/null
+++ b/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+    name: "CtsLegacyStorageRestrictedWithSharedUid",
+    defaults: ["cts_defaults"],
+
+    sdk_version: "current",
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+        "cts_instant",
+    ]
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/AndroidManifest.xml b/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/AndroidManifest.xml
new file mode 100644
index 0000000..5791147
--- /dev/null
+++ b/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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.permission2.cts.legacystoragewithshareduid.restricted"
+    android:versionCode="1"
+    android:sharedUserId="android.permission2.cts.restrictedpermissionuser.shareduid">
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <application android:label="CtsLegacyStorageRestrictedWithSharedUid" />
+</manifest>
diff --git a/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp b/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp
new file mode 100644
index 0000000..9806571
--- /dev/null
+++ b/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+    name: "CtsSMSNotRestrictedWithSharedUid",
+    defaults: ["cts_defaults"],
+
+    sdk_version: "current",
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+        "cts_instant",
+    ]
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/AndroidManifest.xml b/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/AndroidManifest.xml
new file mode 100644
index 0000000..83c43a2
--- /dev/null
+++ b/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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.permission2.cts.smswithshareduid.notrestricted"
+    android:versionCode="1"
+    android:sharedUserId="android.permission2.cts.restrictedpermissionuser.shareduid">
+
+    <uses-permission android:name="android.permission.READ_SMS" />
+
+    <application android:label="CtsSMSNotRestrictedWithSharedUid" />
+</manifest>
diff --git a/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp b/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp
new file mode 100644
index 0000000..ec6d128
--- /dev/null
+++ b/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test_helper_app {
+    name: "CtsSMSRestrictedWithSharedUid",
+    defaults: ["cts_defaults"],
+
+    sdk_version: "current",
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+        "cts_instant",
+    ]
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/AndroidManifest.xml b/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/AndroidManifest.xml
new file mode 100644
index 0000000..a497392
--- /dev/null
+++ b/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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.permission2.cts.smswithshareduid.restricted"
+    android:versionCode="1"
+    android:sharedUserId="android.permission2.cts.restrictedpermissionuser.shareduid">
+
+    <uses-permission android:name="android.permission.READ_SMS" />
+
+    <application android:label="CtsSMSRestrictedWithSharedUid" />
+</manifest>
diff --git a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
index 996630a..7dca607 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
@@ -16,6 +16,7 @@
 
 package android.permission2.cts;
 
+import static android.Manifest.permission.READ_SMS;
 import static android.permission.cts.PermissionUtils.eventually;
 import static android.permission.cts.PermissionUtils.isGranted;
 import static android.permission.cts.PermissionUtils.isPermissionGranted;
@@ -104,6 +105,18 @@
 
     private static final String PKG = "android.permission2.cts.restrictedpermissionuser";
 
+    private static final String APK_USES_SMS_RESTRICTED_SHARED_UID =
+            "/data/local/tmp/cts/permissions2/CtsSMSRestrictedWithSharedUid.apk";
+
+    private static final String PKG_USES_SMS_RESTRICTED_SHARED_UID =
+            "android.permission2.cts.smswithshareduid.restricted";
+
+    private static final String APK_USES_SMS_NOT_RESTRICTED_SHARED_UID =
+            "/data/local/tmp/cts/permissions2/CtsSMSNotRestrictedWithSharedUid.apk";
+
+    private static final String PKG_USES_SMS_NOT_RESTRICTED_SHARED_UID =
+            "android.permission2.cts.smswithshareduid.notrestricted";
+
     private static final long UI_TIMEOUT = 5000L;
 
     private static @NonNull BroadcastReceiver sCommandReceiver;
@@ -681,6 +694,19 @@
         assertNoRestrictedPermissionWhitelisted();
     }
 
+    @Test
+    @AppModeFull
+    public void shareUidBetweenRestrictedAndNotRestrictedApp() throws Exception {
+        runShellCommand(
+                "pm install -g --restrict-permissions " + APK_USES_SMS_RESTRICTED_SHARED_UID);
+        runShellCommand("pm install -g " + APK_USES_SMS_NOT_RESTRICTED_SHARED_UID);
+
+        eventually(
+                () -> assertThat(isGranted(PKG_USES_SMS_RESTRICTED_SHARED_UID, READ_SMS)).isTrue());
+        // The apps share a UID, hence the whitelisting is shared too
+        assertThat(isGranted(PKG_USES_SMS_NOT_RESTRICTED_SHARED_UID, READ_SMS)).isTrue();
+    }
+
     private static void installRestrictedPermissionUserApp(@NonNull SessionParams params)
             throws Exception {
         final CountDownLatch installLatch = new CountDownLatch(1);
@@ -1113,6 +1139,8 @@
     @After
     public void uninstallApp() {
         runShellCommand("pm uninstall " + PKG);
+        runShellCommand("pm uninstall " + PKG_USES_SMS_NOT_RESTRICTED_SHARED_UID);
+        runShellCommand("pm uninstall " + PKG_USES_SMS_RESTRICTED_SHARED_UID);
     }
 
     private static @NonNull Context getContext() {
diff --git a/tests/tests/permission2/src/android/permission2/cts/RestrictedStoragePermissionSharedUidTest.java b/tests/tests/permission2/src/android/permission2/cts/RestrictedStoragePermissionSharedUidTest.java
new file mode 100644
index 0000000..1d0ded2
--- /dev/null
+++ b/tests/tests/permission2/src/android/permission2/cts/RestrictedStoragePermissionSharedUidTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2019 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.permission2.cts;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
+import static android.permission.cts.PermissionUtils.eventually;
+import static android.permission.cts.PermissionUtils.isGranted;
+import static android.permission2.cts.RestrictedStoragePermissionSharedUidTest.StorageState.DENIED;
+import static android.permission2.cts.RestrictedStoragePermissionSharedUidTest.StorageState.ISOLATED;
+import static android.permission2.cts.RestrictedStoragePermissionSharedUidTest.StorageState.NON_ISOLATED;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.lang.Integer.min;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+
+@AppModeFull(reason = "Instant apps cannot access other app's properties")
+@RunWith(Parameterized.class)
+public class RestrictedStoragePermissionSharedUidTest {
+    private static final String LOG_TAG =
+            RestrictedStoragePermissionSharedUidTest.class.getSimpleName();
+
+    public enum StorageState {
+        /** The app has non-isolated storage */
+        NON_ISOLATED,
+
+        /** The app has isolated storage */
+        ISOLATED,
+
+        /** The read-external-storage permission cannot be granted */
+        DENIED
+    }
+
+    /**
+     * An app that is tested
+     */
+    private static class TestApp {
+        private static @NonNull Context sContext =
+                InstrumentationRegistry.getInstrumentation().getContext();
+        private static @NonNull AppOpsManager sAppOpsManager =
+                sContext.getSystemService(AppOpsManager.class);
+        private static @NonNull PackageManager sPackageManager = sContext.getPackageManager();
+
+        private final String mApk;
+        private final String mPkg;
+
+        public final boolean isRestricted;
+        public final boolean hasRequestedLegacyExternalStorage;
+
+        TestApp(@NonNull String apk, @NonNull String pkg, boolean isRestricted,
+                @NonNull boolean hasRequestedLegacyExternalStorage) {
+            mApk = apk;
+            mPkg = pkg;
+
+            this.isRestricted = isRestricted;
+            this.hasRequestedLegacyExternalStorage = hasRequestedLegacyExternalStorage;
+        }
+
+        /**
+         * Assert that the read-external-storage permission was granted or not granted.
+         *
+         * @param expectGranted {@code true} if the permission is expected to be granted
+         */
+        void assertStoragePermGranted(boolean expectGranted) {
+            eventually(() -> assertThat(isGranted(mPkg, READ_EXTERNAL_STORAGE)).named(
+                    this + " read storage granted").isEqualTo(expectGranted));
+        }
+
+        /**
+         * Assert that the app has non-isolated storage
+         *
+         * @param expectGranted {@code true} if the app is expected to have non-isolated storage
+         */
+        void assertHasNotIsolatedStorage(boolean expectHasNotIsolatedStorage) {
+            eventually(() -> runWithShellPermissionIdentity(() -> {
+                int uid = sContext.getPackageManager().getPackageUid(mPkg, 0);
+                if (expectHasNotIsolatedStorage) {
+                    assertThat(sAppOpsManager.unsafeCheckOpRawNoThrow(OPSTR_LEGACY_STORAGE, uid,
+                            mPkg)).named(this + " legacy storage mode").isEqualTo(MODE_ALLOWED);
+                } else {
+                    assertThat(sAppOpsManager.unsafeCheckOpRawNoThrow(OPSTR_LEGACY_STORAGE, uid,
+                            mPkg)).named(this + " legacy storage mode").isNotEqualTo(MODE_ALLOWED);
+                }
+            }));
+        }
+
+        int getTargetSDK() throws Exception {
+            return sPackageManager.getApplicationInfo(mPkg, 0).targetSdkVersion;
+        }
+
+        void install() {
+            if (isRestricted) {
+                runShellCommand("pm install -g --restrict-permissions " + mApk);
+            } else {
+                runShellCommand("pm install -g " + mApk);
+            }
+        }
+
+        void uninstall() {
+            runShellCommand("pm uninstall " + mPkg);
+        }
+
+        @Override
+        public String toString() {
+            return mPkg.substring(PKG_PREFIX.length());
+        }
+    }
+
+    /**
+     * Placeholder for "no app". The properties are chosen that when combined with another app, the
+     * other app always decides the resulting property,
+     */
+    private static class NoApp extends TestApp {
+        NoApp() {
+            super("", PKG_PREFIX + "(none)", true, false);
+        }
+
+        void assertStoragePermGranted(boolean ignored) {
+            // empty
+        }
+
+        void assertHasNotIsolatedStorage(boolean ignored) {
+            // empty
+        }
+
+        @Override
+        int getTargetSDK() {
+            return 10000;
+        }
+
+        @Override
+        public void install() {
+            // empty
+        }
+
+        @Override
+        public void uninstall() {
+            // empty
+        }
+    }
+
+    private static final String APK_PATH = "/data/local/tmp/cts/permissions2/";
+    private static final String PKG_PREFIX = "android.permission2.cts.legacystoragewithshareduid.";
+
+    private static final TestApp[] TEST_APPS = new TestApp[]{
+            new TestApp(APK_PATH + "CtsLegacyStorageNotIsolatedWithSharedUid.apk",
+                    PKG_PREFIX + "notisolated", false, true),
+            new TestApp(APK_PATH + "CtsLegacyStorageIsolatedWithSharedUid.apk",
+                    PKG_PREFIX + "isolated", false, false),
+            new TestApp(APK_PATH + "CtsLegacyStorageRestrictedWithSharedUid.apk",
+                    PKG_PREFIX + "restricted", true, false),
+            new TestApp(APK_PATH + "CtsLegacyStorageRestrictedSdk28WithSharedUid.apk",
+                    PKG_PREFIX + "restrictedsdk28", true, true),
+            new NoApp()};
+
+    /**
+     * First app to be tested. This is the first in an entry created by {@link
+     * #getTestAppCombinations}
+     */
+    @Parameter(0)
+    public @NonNull TestApp app1;
+
+    /**
+     * Second app to be tested. This is the second in an entry created by {@link
+     * #getTestAppCombinations}
+     */
+    @Parameter(1)
+    public @NonNull TestApp app2;
+
+    /**
+     * Run this test for all combination of two tests-apps out of {@link #TEST_APPS}. This includes
+     * the {@link NoApp}, i.e. we also test a single test-app by itself.
+     *
+     * @return All combinations of two test-apps
+     */
+    @Parameters(name = "{0} and {1}")
+    public static Iterable<Object[]> getTestAppCombinations() {
+        ArrayList<Object[]> parameters = new ArrayList<>();
+
+        for (int firstApp = 0; firstApp < TEST_APPS.length; firstApp++) {
+            for (int secondApp = firstApp + 1; secondApp < TEST_APPS.length; secondApp++) {
+                parameters.add(new Object[]{TEST_APPS[firstApp], TEST_APPS[secondApp]});
+            }
+        }
+
+        return parameters;
+    }
+
+    @Test
+    public void checkExceptedStorageStateForAppsSharingUid() throws Exception {
+        app1.install();
+        app2.install();
+
+        int targetSDK = min(app1.getTargetSDK(), app2.getTargetSDK());
+        boolean isRestricted = app1.isRestricted && app2.isRestricted;
+        boolean hasRequestedLegacyExternalStorage =
+                app1.hasRequestedLegacyExternalStorage || app2.hasRequestedLegacyExternalStorage;
+
+        StorageState expectedState;
+        if (isRestricted) {
+            if (targetSDK < Build.VERSION_CODES.Q) {
+                expectedState = DENIED;
+            } else {
+                expectedState = ISOLATED;
+            }
+        } else if (hasRequestedLegacyExternalStorage) {
+            expectedState = NON_ISOLATED;
+        } else {
+            expectedState = ISOLATED;
+        }
+
+        Log.i(LOG_TAG, "Expected state=" + expectedState);
+
+        app1.assertStoragePermGranted(expectedState != DENIED);
+        app2.assertStoragePermGranted(expectedState != DENIED);
+
+        if (expectedState != DENIED) {
+            app1.assertHasNotIsolatedStorage(expectedState == NON_ISOLATED);
+            app2.assertHasNotIsolatedStorage(expectedState == NON_ISOLATED);
+        }
+    }
+
+    @After
+    public void uninstallAllTestPackages() {
+        app1.uninstall();
+        app2.uninstall();
+    }
+}