Merge "Manifest flag for apps to request sandboxing."
diff --git a/api/current.txt b/api/current.txt
index 23cf2a3..db17542 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -291,6 +291,7 @@
     field public static final int allowBackup = 16843392; // 0x1010280
     field public static final int allowClearUserData = 16842757; // 0x1010005
     field public static final int allowEmbedded = 16843765; // 0x10103f5
+    field public static final int allowExternalStorageSandbox = 16844201; // 0x10105a9
     field public static final int allowParallelSyncs = 16843570; // 0x1010332
     field public static final int allowSingleTap = 16843353; // 0x1010259
     field public static final int allowTaskReparenting = 16843268; // 0x1010204
@@ -34659,9 +34660,11 @@
     method @NonNull public static java.io.File getRootDirectory();
     method @Deprecated public static String getStorageState(java.io.File);
     method public static boolean isExternalStorageEmulated();
-    method public static boolean isExternalStorageEmulated(java.io.File);
+    method public static boolean isExternalStorageEmulated(@NonNull java.io.File);
     method public static boolean isExternalStorageRemovable();
-    method public static boolean isExternalStorageRemovable(java.io.File);
+    method public static boolean isExternalStorageRemovable(@NonNull java.io.File);
+    method public static boolean isExternalStorageSandboxed();
+    method public static boolean isExternalStorageSandboxed(@NonNull java.io.File);
     field public static String DIRECTORY_ALARMS;
     field public static String DIRECTORY_AUDIOBOOKS;
     field public static String DIRECTORY_DCIM;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 068a93a..5328dda 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -678,6 +678,14 @@
      */
     public static final int PRIVATE_FLAG_IS_RESOURCE_OVERLAY = 1 << 28;
 
+    /**
+     * Value for {@link #privateFlags}: If {@code true} this app allows
+     * shared/external storage media to be a sandboxed view that only contains
+     * files owned by the app.
+     *
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX = 1 << 29;
 
     /** @hide */
     @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = {
@@ -707,7 +715,8 @@
             PRIVATE_FLAG_VIRTUAL_PRELOAD,
             PRIVATE_FLAG_HAS_FRAGILE_USER_DATA,
             PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE,
-            PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE
+            PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE,
+            PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApplicationInfoPrivateFlags {}
@@ -1822,6 +1831,16 @@
         return (privateFlags & PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE) != 0;
     }
 
+    /**
+     * If {@code true} this app allows shared/external storage media to be a
+     * sandboxed view that only contains files owned by the app.
+     *
+     * @hide
+     */
+    public boolean isExternalStorageSandboxAllowed() {
+        return (privateFlags & PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX) != 0;
+    }
+
     private boolean isAllowedToUseHiddenApis() {
         if (isSignedWithPlatformKey()) {
             return true;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 35d1eac..0a01dcd 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3689,6 +3689,12 @@
             ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE;
         }
 
+        if (sa.getBoolean(
+                R.styleable.AndroidManifestApplication_allowExternalStorageSandbox,
+                owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q)) {
+            ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX;
+        }
+
         ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0);
         ai.minAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_minAspectRatio, 0);
 
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index cceb6ed..f7e927e 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -20,6 +20,8 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.os.storage.StorageManager;
@@ -1060,7 +1062,7 @@
      * @throws IllegalArgumentException if the path is not a valid storage
      *             device.
      */
-    public static boolean isExternalStorageRemovable(File path) {
+    public static boolean isExternalStorageRemovable(@NonNull File path) {
         final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
         if (volume != null) {
             return volume.isRemovable();
@@ -1103,7 +1105,7 @@
      * @throws IllegalArgumentException if the path is not a valid storage
      *             device.
      */
-    public static boolean isExternalStorageEmulated(File path) {
+    public static boolean isExternalStorageEmulated(@NonNull File path) {
         final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
         if (volume != null) {
             return volume.isEmulated();
@@ -1112,6 +1114,44 @@
         }
     }
 
+    /**
+     * Returns whether the shared/external storage media at the given path is a
+     * sandboxed view that only contains files owned by the app.
+     * <p>
+     * This value may be different from the value requested by
+     * {@code allowExternalStorageSandbox} in the app's manifest, since an app
+     * may inherit its sandboxed state based on when it was first installed.
+     * <p>
+     * Sandboxed apps can continue to discover and read media belonging to other
+     * apps via {@link android.provider.MediaStore}.
+     */
+    public static boolean isExternalStorageSandboxed() {
+        final File externalDir = sCurrentUser.getExternalDirs()[0];
+        return isExternalStorageSandboxed(externalDir);
+    }
+
+    /**
+     * Returns whether the shared/external storage media at the given path is a
+     * sandboxed view that only contains files owned by the app.
+     * <p>
+     * This value may be different from the value requested by
+     * {@code allowExternalStorageSandbox} in the app's manifest, since an app
+     * may inherit its sandboxed state based on when it was first installed.
+     * <p>
+     * Sandboxed apps can continue to discover and read media belonging to other
+     * apps via {@link android.provider.MediaStore}.
+     *
+     * @throws IllegalArgumentException if the path is not a valid storage
+     *             device.
+     */
+    public static boolean isExternalStorageSandboxed(@NonNull File path) {
+        final Context context = AppGlobals.getInitialApplication();
+        final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
+        return appOps.noteOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE,
+                context.getApplicationInfo().uid,
+                context.getPackageName()) != AppOpsManager.MODE_ALLOWED;
+    }
+
     static File getDirectory(String variableName, String defaultPath) {
         String path = System.getenv(variableName);
         return path == null ? new File(defaultPath) : new File(path);
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 362d01c..8f3f25a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1687,6 +1687,17 @@
                  - {@code false} for apps with targetSdkVersion < 29.
              -->
         <attr name="allowAudioPlaybackCapture" format="boolean" />
+        <!-- If {@code true} this app allows shared/external storage media to be
+             a sandboxed view that only contains files owned by the app.
+             <p>
+             Sandboxed apps can continue to discover and read media belonging to other
+             apps via {@code MediaStore}.
+             <p>
+             The default value is:
+                 - {@code true} for apps with targetSdkVersion >= 29 (Q).
+                 - {@code false} for apps with targetSdkVersion < 29.
+             -->
+        <attr name="allowExternalStorageSandbox" format="boolean" />
     </declare-styleable>
     <!-- The <code>permission</code> tag declares a security permission that can be
          used to control access from other packages to specific components or
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3505994..3bbc03f 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2942,6 +2942,7 @@
         <public name="allowClearUserDataOnFailedRestore"/>
         <public name="allowAudioPlaybackCapture"/>
         <public name="secureElementName" />
+        <public name="allowExternalStorageSandbox"/>
     </public-group>
 
     <public-group type="drawable" first-id="0x010800b4">
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 096335e..6a1f223 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4383,6 +4383,7 @@
             ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION, "PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION",
             ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE, "PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE",
             ApplicationInfo.PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE, "ALLOW_AUDIO_PLAYBACK_CAPTURE",
+            ApplicationInfo.PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX, "ALLOW_EXTERNAL_STORAGE_SANDBOX",
             ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND, "BACKUP_IN_FOREGROUND",
             ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
             ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE, "DEFAULT_TO_DEVICE_PROTECTED_STORAGE",