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",