Merge "Update storage permission policy to use AppCompat APIs."
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index d754380..a83c58d 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -27,16 +27,22 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static java.lang.Integer.min;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.os.Build;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.compat.IPlatformCompat;
/**
* The behavior of soft restricted permissions is different for each permission. This class collects
@@ -46,6 +52,27 @@
* {@link com.android.packageinstaller.permission.utils.SoftRestrictedPermissionPolicy}
*/
public abstract class SoftRestrictedPermissionPolicy {
+ /**
+ * Enables scoped storage, with exceptions for apps that explicitly request legacy access, or
+ * apps that hold the {@code android.Manifest.permission#WRITE_MEDIA_STORAGE} permission.
+ * See https://developer.android.com/training/data-storage#scoped-storage for more information.
+ */
+ @ChangeId
+ // This change is enabled for apps with targetSDK > {@link android.os.Build.VERSION_CODES.P}
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.P)
+ static final long ENABLE_SCOPED_STORAGE = 144914977L;
+
+ /**
+ * Enforces scoped storage for all apps, preventing individual apps from opting out. This change
+ * has precedence over {@code ENABLE_SCOPED_STORAGE}.
+ */
+ @ChangeId
+ // This change is enabled for apps with targetSDK > {@link android.os.Build.VERSION_CODES.Q}.
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.Q)
+ static final long REQUIRE_SCOPED_STORAGE = 131432978L;
+
+ private static final String LOG_TAG = SoftRestrictedPermissionPolicy.class.getSimpleName();
+
private static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
| FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
@@ -60,41 +87,6 @@
};
/**
- * TargetSDK is per package. To make sure two apps int the same shared UID do not fight over
- * what to set, always compute the combined targetSDK.
- *
- * @param context A context
- * @param appInfo The app that is changed
- * @param user The user the app belongs to
- *
- * @return The minimum targetSDK of all apps sharing the uid of the app
- */
- private static int getMinimumTargetSDK(@NonNull Context context,
- @NonNull ApplicationInfo appInfo, @NonNull UserHandle user) {
- PackageManager pm = context.getPackageManager();
-
- int minimumTargetSDK = appInfo.targetSdkVersion;
-
- String[] uidPkgs = pm.getPackagesForUid(appInfo.uid);
- if (uidPkgs != null) {
- for (String uidPkg : uidPkgs) {
- if (!uidPkg.equals(appInfo.packageName)) {
- ApplicationInfo uidPkgInfo;
- try {
- uidPkgInfo = pm.getApplicationInfoAsUser(uidPkg, 0, user);
- } catch (PackageManager.NameNotFoundException e) {
- continue;
- }
-
- minimumTargetSDK = min(minimumTargetSDK, uidPkgInfo.targetSdkVersion);
- }
- }
- }
-
- return minimumTargetSDK;
- }
-
- /**
* Get the policy for a soft restricted permission.
*
* @param context A context to use
@@ -114,27 +106,31 @@
case READ_EXTERNAL_STORAGE: {
final boolean isWhiteListed;
boolean shouldApplyRestriction;
- final int targetSDK;
final boolean hasRequestedLegacyExternalStorage;
final boolean hasWriteMediaStorageGrantedForUid;
+ final boolean isScopedStorageEnabled;
if (appInfo != null) {
PackageManager pm = context.getPackageManager();
int flags = pm.getPermissionFlags(permission, appInfo.packageName, user);
isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
- targetSDK = getMinimumTargetSDK(context, appInfo, user);
- shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0
- || targetSDK > Build.VERSION_CODES.Q;
hasRequestedLegacyExternalStorage = hasUidRequestedLegacyExternalStorage(
appInfo.uid, context);
hasWriteMediaStorageGrantedForUid = hasWriteMediaStorageGrantedForUid(
appInfo.uid, context);
+ final boolean isScopedStorageRequired =
+ isChangeEnabledForUid(context, appInfo, user, REQUIRE_SCOPED_STORAGE);
+ isScopedStorageEnabled =
+ isChangeEnabledForUid(context, appInfo, user, ENABLE_SCOPED_STORAGE)
+ || isScopedStorageRequired;
+ shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0
+ || isScopedStorageRequired;
} else {
isWhiteListed = false;
shouldApplyRestriction = false;
- targetSDK = 0;
hasRequestedLegacyExternalStorage = false;
hasWriteMediaStorageGrantedForUid = false;
+ isScopedStorageEnabled = false;
}
// We have a check in PermissionPolicyService.PermissionToOpSynchroniser.setUidMode
@@ -144,7 +140,7 @@
return new SoftRestrictedPermissionPolicy() {
@Override
public boolean mayGrantPermission() {
- return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
+ return isWhiteListed || isScopedStorageEnabled;
}
@Override
public int getExtraAppOpCode() {
@@ -152,7 +148,7 @@
}
@Override
public boolean mayAllowExtraAppOp() {
- return !shouldApplyRestriction && targetSDK <= Build.VERSION_CODES.Q
+ return !shouldApplyRestriction
&& (hasRequestedLegacyExternalStorage
|| hasWriteMediaStorageGrantedForUid);
}
@@ -164,22 +160,26 @@
}
case WRITE_EXTERNAL_STORAGE: {
final boolean isWhiteListed;
- final int targetSDK;
+ final boolean isScopedStorageEnabled;
if (appInfo != null) {
final int flags = context.getPackageManager().getPermissionFlags(permission,
appInfo.packageName, user);
isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
- targetSDK = getMinimumTargetSDK(context, appInfo, user);
+ final boolean isScopedStorageRequired =
+ isChangeEnabledForUid(context, appInfo, user, REQUIRE_SCOPED_STORAGE);
+ isScopedStorageEnabled =
+ isChangeEnabledForUid(context, appInfo, user, ENABLE_SCOPED_STORAGE)
+ || isScopedStorageRequired;
} else {
isWhiteListed = false;
- targetSDK = 0;
+ isScopedStorageEnabled = false;
}
return new SoftRestrictedPermissionPolicy() {
@Override
public boolean mayGrantPermission() {
- return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
+ return isWhiteListed || isScopedStorageEnabled;
}
};
}
@@ -188,6 +188,62 @@
}
}
+ /**
+ * Checks whether an AppCompat change is enabled for all packages sharing a UID with the
+ * provided application.
+ *
+ * @param context A context to use.
+ * @param appInfo The application for which to check whether the compat change is enabled.
+ * @param user The user the app belongs to.
+ * @param changeId A {@link android.compat.annotation.ChangeId} corresponding to the change.
+ *
+ * @return true if this change is enabled for all apps sharing the UID of the provided app,
+ * false otherwise.
+ */
+ private static boolean isChangeEnabledForUid(@NonNull Context context,
+ @NonNull ApplicationInfo appInfo, @NonNull UserHandle user, long changeId) {
+ PackageManager pm = context.getPackageManager();
+
+ String[] uidPackages = pm.getPackagesForUid(appInfo.uid);
+ if (uidPackages != null) {
+ for (String uidPackage : uidPackages) {
+ ApplicationInfo uidPackageInfo;
+ try {
+ uidPackageInfo = pm.getApplicationInfoAsUser(uidPackage, 0, user);
+ } catch (PackageManager.NameNotFoundException e) {
+ continue;
+ }
+ if (!isChangeEnabled(uidPackageInfo, changeId)) {
+ // At least one package sharing this UID does not have this change enabled.
+ return false;
+ }
+ }
+ // All packages sharing this UID returned true for {@link #isChangeEnabled()}.
+ return true;
+ } else {
+ Log.w(LOG_TAG, "Check for change " + changeId + " for uid " + appInfo.uid
+ + " produced no packages. Defaulting to using the information for "
+ + appInfo.packageName + " only.");
+ return isChangeEnabled(appInfo, changeId);
+ }
+ }
+
+ private static boolean isChangeEnabled(@NonNull ApplicationInfo appInfo, long changeId) {
+ IBinder binder = ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
+ IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(binder);
+
+ final long callingId = Binder.clearCallingIdentity();
+
+ try {
+ return platformCompat.isChangeEnabled(changeId, appInfo);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Check for change " + changeId + " failed. Defaulting to enabled.", e);
+ return true;
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
private static boolean hasUidRequestedLegacyExternalStorage(int uid, @NonNull Context context) {
PackageManager packageManager = context.getPackageManager();
String[] packageNames = packageManager.getPackagesForUid(uid);