Make sure we preserve preserveLegacyExternalStorage.

This could be lost after a reboot, due to the following sequence of
events:

1) App targets SDK 29 and has LEGACY_STORAGE
2) App updates to a version with targetSdk30 and preserveLegacyExternalStorage
3) App maintains LEGACY_STORAGE, because we currently have it and preserve
was requested
4) We reboot
5) When evaluating the READ_EXTERNAL_STORAGE permission, we check
whether we should grant the LEGACY_STORAGE extra app-op by calling
mayAllowExtraAppOp(); this call returns false, because there's a
check whether the app *currently* has LEGACY_STORAGE, which isn't true.
6) We then check whether we should deny LEGACY_STORAGE if it was
previously granted; this returns true, because it was implemented as the
inverse of 5)
7) LEGACY_STORAGE is denied

Fix this by more explicitly coding what allows us to get the appop, and
how it can be removed once we already have it.

Bug: 169943139
Test: atest RestrictedStoragePermissionTest
Test: atest PreserveLegacyStorageHostTest
Change-Id: Ic24372348118ad9ed818a28f377e0decc78b9ecc
Merged-In: Ic24372348118ad9ed818a28f377e0decc78b9ecc
(cherry picked from commit c57e3455ff29b0a15a8781a34c19c6ded196f69e)
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index cc36935..9026262 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -127,9 +127,11 @@
                 final boolean isWhiteListed;
                 boolean shouldApplyRestriction;
                 final int targetSDK;
+                final boolean hasLegacyExternalStorage;
                 final boolean hasRequestedLegacyExternalStorage;
-                final boolean shouldPreserveLegacyExternalStorage;
+                final boolean hasRequestedPreserveLegacyExternalStorage;
                 final boolean hasWriteMediaStorageGrantedForUid;
+                final boolean isForcedScopedStorage;
 
                 if (appInfo != null) {
                     PackageManager pm = context.getPackageManager();
@@ -137,27 +139,27 @@
                             LocalServices.getService(StorageManagerInternal.class);
                     int flags = pm.getPermissionFlags(permission, appInfo.packageName, user);
                     isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
+                    hasLegacyExternalStorage = smInternal.hasLegacyExternalStorage(appInfo.uid);
                     hasRequestedLegacyExternalStorage = hasUidRequestedLegacyExternalStorage(
                             appInfo.uid, context);
                     hasWriteMediaStorageGrantedForUid = hasWriteMediaStorageGrantedForUid(
                             appInfo.uid, context);
-                    shouldPreserveLegacyExternalStorage = pkg.hasPreserveLegacyExternalStorage()
-                            && smInternal.hasLegacyExternalStorage(appInfo.uid);
+                    hasRequestedPreserveLegacyExternalStorage =
+                            pkg.hasPreserveLegacyExternalStorage();
                     targetSDK = getMinimumTargetSDK(context, appInfo, user);
 
-                    shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0
-                            || (targetSDK > Build.VERSION_CODES.Q
-                            && !shouldPreserveLegacyExternalStorage)
-                            // If the device is configured to force this app into scoped storage,
-                            // then we should apply the restriction
-                            || sForcedScopedStorageAppWhitelist.contains(appInfo.packageName);
+                    shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+                    isForcedScopedStorage = sForcedScopedStorageAppWhitelist
+                            .contains(appInfo.packageName);
                 } else {
                     isWhiteListed = false;
                     shouldApplyRestriction = false;
                     targetSDK = 0;
+                    hasLegacyExternalStorage = false;
                     hasRequestedLegacyExternalStorage = false;
-                    shouldPreserveLegacyExternalStorage = false;
+                    hasRequestedPreserveLegacyExternalStorage = false;
                     hasWriteMediaStorageGrantedForUid = false;
+                    isForcedScopedStorage = false;
                 }
 
                 // We have a check in PermissionPolicyService.PermissionToOpSynchroniser.setUidMode
@@ -175,14 +177,53 @@
                     }
                     @Override
                     public boolean mayAllowExtraAppOp() {
-                        return !shouldApplyRestriction
-                                && (hasRequestedLegacyExternalStorage
-                                        || hasWriteMediaStorageGrantedForUid
-                                        || shouldPreserveLegacyExternalStorage);
+                        // The only way to get LEGACY_STORAGE (if you didn't already have it)
+                        // is that all of the following must be true:
+                        // 1. The flag shouldn't be restricted
+                        if (shouldApplyRestriction) {
+                            return false;
+                        }
+
+                        // 2. The app shouldn't be in sForcedScopedStorageAppWhitelist
+                        if (isForcedScopedStorage) {
+                            return false;
+                        }
+
+                        // 3. The app has WRITE_MEDIA_STORAGE, OR
+                        //      the app already has legacy external storage or requested it,
+                        //      and is < R.
+                        return hasWriteMediaStorageGrantedForUid
+                                || ((hasLegacyExternalStorage || hasRequestedLegacyExternalStorage)
+                                    && targetSDK < Build.VERSION_CODES.R);
                     }
                     @Override
                     public boolean mayDenyExtraAppOpIfGranted() {
-                        return shouldApplyRestriction;
+                        // If you're an app targeting < R, you can keep the app op for
+                        // as long as you meet the conditions required to acquire it.
+                        if (targetSDK < Build.VERSION_CODES.R) {
+                            return !mayAllowExtraAppOp();
+                        }
+
+                        // For an app targeting R, the only way to lose LEGACY_STORAGE if you
+                        // already had it is in one or more of the following conditions:
+                        // 1. The flag became restricted
+                        if (shouldApplyRestriction) {
+                            return true;
+                        }
+
+                        // The package is now a part of the forced scoped storage whitelist
+                        if (isForcedScopedStorage) {
+                            return true;
+                        }
+
+                        // The package doesn't have WRITE_MEDIA_STORAGE,
+                        // AND didn't request legacy storage to be preserved
+                        if (!hasWriteMediaStorageGrantedForUid
+                                && !hasRequestedPreserveLegacyExternalStorage) {
+                            return true;
+                        }
+
+                        return false;
                     }
                 };
             }