Manifest flag for apps to request sandboxing.

We're iterating on the design to let apps request via a manifest
flag if they'd like to opt-in or opt-out of storage sandboxing.  The
default is to assume that P apps don't allow sandboxing, and that
Q apps do allow sandboxing.

Bug: 129487770
Test: manual
Change-Id: I8fddb35eed412c78622be4ccae57c7b7d2aa1286
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);