Better definition of "system" as "self".

Over the last few years, we established a clear boundary between
third-party apps and "system" internals, so that we could start
tracking down all hidden API usages.

As part of becoming a Mainline module in Android R, we need to
reduce this boundary even further to make it clear that the only
component able to use hidden API details is the MediaProvider
itself.

If we didn't have this enforcement in place, we'd slowly build up
land mines of hidden API usage by still-bundled system components
that would prevent us from being able to safely deploy Mainline
updates in the future.

This change adjusts the existing "backup" feature to become a
more general-purpose "delegator" permission, which better matches
what system apps are trying to do: they're simply interested in
shifting ownership of an existing media item to another package.

This use-case blends nicely with the existing UPDATE_DEVICE_STATS
permission, which is designed for shifting blame/ownership of
things like CPU and network traffic to other apps, and thankfully
DownloadManager already holds this permission, so this gives us
a nice clean way of keeping DownloadManager working.

Fixes a few additional bugs where the shell and delegators should
be able to perform certain tasks.

Bug: 155347675
Test: atest --test-mapping packages/providers/MediaProvider
Test: atest --test-mapping packages/providers/DownloadProvider
Change-Id: I7b1759642466ae64d6d643716743cfdb7136916b
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index 9b13c38..6f554b4 100644
--- a/src/com/android/providers/media/LocalCallingIdentity.java
+++ b/src/com/android/providers/media/LocalCallingIdentity.java
@@ -22,13 +22,14 @@
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 
 import static com.android.providers.media.util.PermissionUtils.checkIsLegacyStorageGranted;
-import static com.android.providers.media.util.PermissionUtils.checkPermissionBackup;
-import static com.android.providers.media.util.PermissionUtils.checkPermissionManageExternalStorage;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionDelegator;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionManager;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadAudio;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadImages;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadStorage;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadVideo;
-import static com.android.providers.media.util.PermissionUtils.checkPermissionSystem;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionSelf;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionShell;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteAudio;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteImages;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteStorage;
@@ -132,7 +133,8 @@
         ident.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
         ident.targetSdkVersionResolved = true;
         ident.hasPermission = ~(PERMISSION_IS_LEGACY_GRANTED | PERMISSION_IS_LEGACY_WRITE
-                | PERMISSION_IS_LEGACY_READ | PERMISSION_IS_REDACTION_NEEDED);
+                | PERMISSION_IS_LEGACY_READ | PERMISSION_IS_REDACTION_NEEDED
+                | PERMISSION_IS_SHELL | PERMISSION_IS_DELEGATOR);
         ident.hasPermissionResolved = ~0;
         return ident;
     }
@@ -194,19 +196,22 @@
         return Build.VERSION_CODES.CUR_DEVELOPMENT;
     }
 
-    public static final int PERMISSION_IS_SYSTEM = 1 << 0;
-    public static final int PERMISSION_IS_LEGACY_WRITE = 1 << 1;
-    public static final int PERMISSION_IS_REDACTION_NEEDED = 1 << 2;
-    public static final int PERMISSION_READ_AUDIO = 1 << 3;
-    public static final int PERMISSION_READ_VIDEO = 1 << 4;
-    public static final int PERMISSION_READ_IMAGES = 1 << 5;
-    public static final int PERMISSION_WRITE_AUDIO = 1 << 6;
-    public static final int PERMISSION_WRITE_VIDEO = 1 << 7;
-    public static final int PERMISSION_WRITE_IMAGES = 1 << 8;
-    public static final int PERMISSION_IS_LEGACY_READ = 1 << 9;
-    public static final int PERMISSION_IS_LEGACY_GRANTED = 1 << 10;
-    public static final int PERMISSION_IS_BACKUP = 1 << 11;
-    public static final int PERMISSION_MANAGE_EXTERNAL_STORAGE = 1 << 12;
+    public static final int PERMISSION_IS_SELF = 1 << 0;
+    public static final int PERMISSION_IS_SHELL = 1 << 1;
+    public static final int PERMISSION_IS_MANAGER = 1 << 2;
+    public static final int PERMISSION_IS_DELEGATOR = 1 << 3;
+
+    public static final int PERMISSION_IS_REDACTION_NEEDED = 1 << 8;
+    public static final int PERMISSION_IS_LEGACY_GRANTED = 1 << 9;
+    public static final int PERMISSION_IS_LEGACY_READ = 1 << 10;
+    public static final int PERMISSION_IS_LEGACY_WRITE = 1 << 11;
+
+    public static final int PERMISSION_READ_AUDIO = 1 << 16;
+    public static final int PERMISSION_READ_VIDEO = 1 << 17;
+    public static final int PERMISSION_READ_IMAGES = 1 << 18;
+    public static final int PERMISSION_WRITE_AUDIO = 1 << 19;
+    public static final int PERMISSION_WRITE_VIDEO = 1 << 20;
+    public static final int PERMISSION_WRITE_IMAGES = 1 << 21;
 
     private int hasPermission;
     private int hasPermissionResolved;
@@ -230,22 +235,30 @@
         }
 
         switch (permission) {
-            case PERMISSION_IS_SYSTEM:
-                return isSystemInternal();
-            case PERMISSION_IS_BACKUP:
-                return isBackupInternal();
-            case PERMISSION_IS_LEGACY_GRANTED:
-                return isLegacyStorageGranted();
-            case PERMISSION_IS_LEGACY_WRITE:
-                return isLegacyWriteInternal();
-            case PERMISSION_IS_LEGACY_READ:
-                return isLegacyReadInternal();
+            case PERMISSION_IS_SELF:
+                return checkPermissionSelf(context, pid, uid);
+            case PERMISSION_IS_SHELL:
+                return checkPermissionShell(context, pid, uid);
+            case PERMISSION_IS_MANAGER:
+                return checkPermissionManager(context, pid, uid, getPackageName(), attributionTag);
+            case PERMISSION_IS_DELEGATOR:
+                return checkPermissionDelegator(context, pid, uid);
+
             case PERMISSION_IS_REDACTION_NEEDED:
                 return isRedactionNeededInternal();
+            case PERMISSION_IS_LEGACY_GRANTED:
+                return isLegacyStorageGranted();
+            case PERMISSION_IS_LEGACY_READ:
+                return isLegacyReadInternal();
+            case PERMISSION_IS_LEGACY_WRITE:
+                return isLegacyWriteInternal();
+
             case PERMISSION_READ_AUDIO:
-                return checkPermissionReadAudio(context, pid, uid, getPackageName(), attributionTag);
+                return checkPermissionReadAudio(
+                        context, pid, uid, getPackageName(), attributionTag);
             case PERMISSION_READ_VIDEO:
-                return checkPermissionReadVideo(context, pid, uid, getPackageName(), attributionTag);
+                return checkPermissionReadVideo(
+                        context, pid, uid, getPackageName(), attributionTag);
             case PERMISSION_READ_IMAGES:
                 return checkPermissionReadImages(
                         context, pid, uid, getPackageName(), attributionTag);
@@ -258,22 +271,11 @@
             case PERMISSION_WRITE_IMAGES:
                 return checkPermissionWriteImages(
                         context, pid, uid, getPackageName(), attributionTag);
-            case PERMISSION_MANAGE_EXTERNAL_STORAGE:
-                return checkPermissionManageExternalStorage(
-                        context, pid, uid, getPackageName(), attributionTag);
             default:
                 return false;
         }
     }
 
-    private boolean isSystemInternal() {
-        return checkPermissionSystem(context, pid, uid, getPackageName());
-    }
-
-    private boolean isBackupInternal() {
-        return checkPermissionBackup(context, pid, uid);
-    }
-
     private boolean isLegacyStorageGranted() {
         boolean defaultScopedStorage = CompatChanges.isChangeEnabled(
                 DEFAULT_SCOPED_STORAGE, getPackageName(), UserHandle.getUserHandleForUid(uid));
@@ -314,7 +316,7 @@
 
     /** System internals or callers holding permission have no redaction */
     private boolean isRedactionNeededInternal() {
-        if (hasPermission(PERMISSION_IS_SYSTEM)) {
+        if (hasPermission(PERMISSION_IS_SELF) || hasPermission(PERMISSION_IS_SHELL)) {
             return false;
         }
 
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 33a4ed5..ebee1b4 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -36,13 +36,14 @@
 
 import static com.android.providers.media.DatabaseHelper.EXTERNAL_DATABASE_NAME;
 import static com.android.providers.media.DatabaseHelper.INTERNAL_DATABASE_NAME;
-import static com.android.providers.media.LocalCallingIdentity.PERMISSION_IS_BACKUP;
+import static com.android.providers.media.LocalCallingIdentity.PERMISSION_IS_DELEGATOR;
 import static com.android.providers.media.LocalCallingIdentity.PERMISSION_IS_LEGACY_GRANTED;
 import static com.android.providers.media.LocalCallingIdentity.PERMISSION_IS_LEGACY_READ;
 import static com.android.providers.media.LocalCallingIdentity.PERMISSION_IS_LEGACY_WRITE;
+import static com.android.providers.media.LocalCallingIdentity.PERMISSION_IS_MANAGER;
 import static com.android.providers.media.LocalCallingIdentity.PERMISSION_IS_REDACTION_NEEDED;
-import static com.android.providers.media.LocalCallingIdentity.PERMISSION_IS_SYSTEM;
-import static com.android.providers.media.LocalCallingIdentity.PERMISSION_MANAGE_EXTERNAL_STORAGE;
+import static com.android.providers.media.LocalCallingIdentity.PERMISSION_IS_SELF;
+import static com.android.providers.media.LocalCallingIdentity.PERMISSION_IS_SHELL;
 import static com.android.providers.media.LocalCallingIdentity.PERMISSION_READ_AUDIO;
 import static com.android.providers.media.LocalCallingIdentity.PERMISSION_READ_IMAGES;
 import static com.android.providers.media.LocalCallingIdentity.PERMISSION_READ_VIDEO;
@@ -66,7 +67,6 @@
 import static com.android.providers.media.util.FileUtils.sanitizePath;
 import static com.android.providers.media.util.Logging.LOGV;
 import static com.android.providers.media.util.Logging.TAG;
-import static com.android.providers.media.util.PermissionUtils.checkPermissionManageExternalStorage;
 
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.OnOpActiveChangedListener;
@@ -126,8 +126,8 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.storage.StorageManager.StorageVolumeCallback;
 import android.os.storage.StorageManager;
+import android.os.storage.StorageManager.StorageVolumeCallback;
 import android.os.storage.StorageVolume;
 import android.preference.PreferenceManager;
 import android.provider.BaseColumns;
@@ -1126,8 +1126,8 @@
     static boolean hasPermissionToClearCaches(Context context, ApplicationInfo ai) {
         PermissionUtils.setOpDescription("clear app cache");
         try {
-            return checkPermissionManageExternalStorage(context, /*pid*/ -1, ai.uid, ai.packageName,
-                    /*attributionTag*/ null);
+            return PermissionUtils.checkPermissionManager(context, /* pid */ -1, ai.uid,
+                    ai.packageName, /* attributionTag */ null);
         } finally {
             PermissionUtils.clearOpDescription();
         }
@@ -2584,7 +2584,7 @@
 
             // Allow apps with MANAGE_EXTERNAL_STORAGE to create files anywhere
             if (!validPath) {
-                validPath = isCallingPackageExternalStorageManager();
+                validPath = isCallingPackageManager();
             }
 
             // Allow system gallery to create image/video files.
@@ -2921,7 +2921,7 @@
 
         if (mimeType != null) {
             values.put(FileColumns.MIME_TYPE, mimeType);
-            if (isCallingPackageSystem() && values.containsKey(FileColumns.MEDIA_TYPE)) {
+            if (isCallingPackageSelf() && values.containsKey(FileColumns.MEDIA_TYPE)) {
                 // Leave FileColumns.MEDIA_TYPE untouched if the caller is ModernMediaScanner and
                 // FileColumns.MEDIA_TYPE is already populated.
             } else if (path != null && shouldFileBeHidden(new File(path))) {
@@ -3047,7 +3047,7 @@
         // names of givenOwnerPackage. If givenOwnerPackage is not CallingIdentity, since
         // DownloadProvider can upsert a row on behalf of app, we should include all shared packages
         // of givenOwnerPackage.
-        if (givenOwnerPackage != null && isCallingPackageSystem() &&
+        if (givenOwnerPackage != null && isCallingPackageDelegator() &&
                 !isCallingIdentitySharedPackageName(givenOwnerPackage)) {
             // Allow DownloadProvider to Upsert if givenOwnerPackage is owner of the db row.
             packages.addAll(Arrays.asList(getSharedPackagesForPackage(givenOwnerPackage)));
@@ -3211,7 +3211,7 @@
             for (String column : sDataColumns.keySet()) {
                 if (!initialValues.containsKey(column)) continue;
 
-                if (isCallingPackageSystem() || isCallingPackageLegacyWrite()) {
+                if (isCallingPackageSelf() || isCallingPackageLegacyWrite()) {
                     // Mutation allowed
                 } else {
                     Log.w(TAG, "Ignoring mutation of  " + column + " from "
@@ -3222,7 +3222,7 @@
 
             path = initialValues.getAsString(MediaStore.MediaColumns.DATA);
 
-            if (!isCallingPackageSystem()) {
+            if (!isCallingPackageSelf()) {
                 initialValues.remove(FileColumns.IS_DOWNLOAD);
             }
 
@@ -3234,14 +3234,23 @@
                 initialValues.putNull(ImageColumns.LONGITUDE);
             }
 
-            if (isCallingPackageSystem() || isCallingPackageBackup()) {
-                // When media inserted by ourselves during a scan, or by a
-                // backup app, the best we can do is guess ownership based on
-                // path when it's not explicitly provided
+            if (isCallingPackageSelf() || isCallingPackageShell()) {
+                // When media inserted by ourselves during a scan, or by the
+                // shell, the best we can do is guess ownership based on path
+                // when it's not explicitly provided
                 ownerPackageName = initialValues.getAsString(FileColumns.OWNER_PACKAGE_NAME);
                 if (TextUtils.isEmpty(ownerPackageName)) {
                     ownerPackageName = extractPathOwnerPackageName(path);
                 }
+            } else if (isCallingPackageDelegator()) {
+                // When caller is a delegator, we handle ownership as a hybrid
+                // of the two other cases: we're willing to accept any ownership
+                // transfer attempted during insert, but we fall back to using
+                // the Binder identity if they don't request a specific owner
+                ownerPackageName = initialValues.getAsString(FileColumns.OWNER_PACKAGE_NAME);
+                if (TextUtils.isEmpty(ownerPackageName)) {
+                    ownerPackageName = getCallingPackageOrSelf();
+                }
             } else {
                 // Remote callers have no direct control over owner column; we force
                 // it be whoever is creating the content.
@@ -3336,7 +3345,7 @@
                 values.put(MediaStore.Audio.Playlists.DATE_ADDED, System.currentTimeMillis() / 1000);
                 // Playlist names are stored as display names, but leave
                 // values untouched if the caller is ModernMediaScanner
-                if (!isCallingPackageSystem()) {
+                if (!isCallingPackageSelf()) {
                     if (values.containsKey(Playlists.NAME)) {
                         values.put(MediaColumns.DISPLAY_NAME, values.getAsString(Playlists.NAME));
                     }
@@ -3554,7 +3563,7 @@
             qb.setDistinct(true);
         }
         qb.setStrict(true);
-        if (isCallingPackageSystem()) {
+        if (isCallingPackageSelf()) {
             // When caller is system, such as the media scanner, we're willing
             // to let them access any columns they want
         } else {
@@ -4312,7 +4321,7 @@
                 }
             }
 
-            if (isFilesTable && !isCallingPackageSystem()) {
+            if (isFilesTable && !isCallingPackageSelf()) {
                 Metrics.logDeletion(volumeName, mCallingIdentity.get().uid,
                         getCallingPackageOrSelf(), count);
             }
@@ -4949,7 +4958,7 @@
             for (String column : sDataColumns.keySet()) {
                 if (!initialValues.containsKey(column)) continue;
 
-                if (isCallingPackageSystem() || isCallingPackageLegacyWrite()) {
+                if (isCallingPackageSelf() || isCallingPackageLegacyWrite()) {
                     // Mutation allowed
                 } else {
                     Log.w(TAG, "Ignoring mutation of  " + column + " from "
@@ -4958,12 +4967,44 @@
                 }
             }
 
-            if (!isCallingPackageSystem()) {
-                Trace.beginSection("filter");
+            // Enforce allowed ownership transfers
+            if (initialValues.containsKey(MediaColumns.OWNER_PACKAGE_NAME)) {
+                if (isCallingPackageSelf() || isCallingPackageShell()) {
+                    // When the caller is the media scanner or the shell, we let
+                    // them change ownership however they see fit; nothing to do
+                } else if (isCallingPackageDelegator()) {
+                    // When the caller is a delegator, allow them to shift
+                    // ownership only when current owner, or when ownerless
+                    final String currentOwner;
+                    final String proposedOwner = initialValues
+                            .getAsString(MediaColumns.OWNER_PACKAGE_NAME);
+                    final Uri genericUri = MediaStore.Files.getContentUri(volumeName,
+                            ContentUris.parseId(uri));
+                    try (Cursor c = queryForSingleItem(genericUri,
+                            new String[] { MediaColumns.OWNER_PACKAGE_NAME }, null, null, null)) {
+                        currentOwner = c.getString(0);
+                    } catch (FileNotFoundException e) {
+                        throw new IllegalStateException(e);
+                    }
+                    final boolean transferAllowed = (currentOwner == null)
+                            || Arrays.asList(getSharedPackagesForPackage(getCallingPackageOrSelf()))
+                                    .contains(currentOwner);
+                    if (transferAllowed) {
+                        Log.v(TAG, "Ownership transfer from " + currentOwner + " to "
+                                + proposedOwner + " allowed");
+                    } else {
+                        Log.w(TAG, "Ownership transfer from " + currentOwner + " to "
+                                + proposedOwner + " blocked");
+                        initialValues.remove(MediaColumns.OWNER_PACKAGE_NAME);
+                    }
+                } else {
+                    // Otherwise no ownership changes are allowed
+                    initialValues.remove(MediaColumns.OWNER_PACKAGE_NAME);
+                }
+            }
 
-                // Remote callers have no direct control over owner column; we
-                // force it be whoever is creating the content.
-                initialValues.remove(MediaColumns.OWNER_PACKAGE_NAME);
+            if (!isCallingPackageSelf()) {
+                Trace.beginSection("filter");
 
                 // We default to filtering mutable columns, except when we know
                 // the single item being updated is pending; when it's finally
@@ -5039,7 +5080,7 @@
             case AUDIO_PLAYLISTS_ID:
                 // Playlist names are stored as display names, but leave
                 // values untouched if the caller is ModernMediaScanner
-                if (!isCallingPackageSystem()) {
+                if (!isCallingPackageSelf()) {
                     if (initialValues.containsKey(Playlists.NAME)) {
                         initialValues.put(MediaColumns.DISPLAY_NAME,
                                 initialValues.getAsString(Playlists.NAME));
@@ -5054,7 +5095,7 @@
         // If we're touching columns that would change placement of a file,
         // blend in current values and recalculate path
         final boolean allowMovement = extras.getBoolean(MediaStore.QUERY_ARG_ALLOW_MOVEMENT,
-                !isCallingPackageSystem());
+                !isCallingPackageSelf());
         if (containsAny(initialValues.keySet(), sPlacementColumns)
                 && !initialValues.containsKey(MediaColumns.DATA)
                 && !isThumbnail
@@ -5076,7 +5117,9 @@
             }
 
             final LocalCallingIdentity token = clearLocalCallingIdentity();
-            try (Cursor c = queryForSingleItem(uri,
+            final Uri genericUri = MediaStore.Files.getContentUri(volumeName,
+                    ContentUris.parseId(uri));
+            try (Cursor c = queryForSingleItem(genericUri,
                     sPlacementColumns.toArray(new String[0]), userWhere, userWhereArgs, null)) {
                 for (int i = 0; i < c.getColumnCount(); i++) {
                     final String column = c.getColumnName(i);
@@ -5157,7 +5200,7 @@
 
         // If we're already doing this update from an internal scan, no need to
         // kick off another no-op scan
-        if (isCallingPackageSystem()) {
+        if (isCallingPackageSelf()) {
             triggerScan = false;
         }
 
@@ -6078,7 +6121,7 @@
             return true;
         }
 
-        if (isCallingPackageExternalStorageManager()) {
+        if (isCallingPackageManager()) {
             return true;
         }
 
@@ -6806,12 +6849,12 @@
 
     private boolean checkCallingPermissionGlobal(Uri uri, boolean forWrite) {
         // System internals can work with all media
-        if (isCallingPackageSystem()) {
+        if (isCallingPackageSelf() || isCallingPackageShell()) {
             return true;
         }
 
         // Apps that have permission to manage external storage can work with all files
-        if (isCallingPackageExternalStorageManager()) {
+        if (isCallingPackageManager()) {
             return true;
         }
 
@@ -7417,6 +7460,7 @@
         sMutableColumns.add(MediaStore.MediaColumns.IS_PENDING);
         sMutableColumns.add(MediaStore.MediaColumns.IS_TRASHED);
         sMutableColumns.add(MediaStore.MediaColumns.IS_FAVORITE);
+        sMutableColumns.add(MediaStore.MediaColumns.OWNER_PACKAGE_NAME);
 
         sMutableColumns.add(MediaStore.Audio.AudioColumns.BOOKMARK);
 
@@ -7527,22 +7571,27 @@
 
     @Deprecated
     private boolean isCallingPackageAllowedHidden() {
-        return isCallingPackageSystem();
+        return isCallingPackageSelf();
     }
 
     @Deprecated
-    private boolean isCallingPackageSystem() {
-        return mCallingIdentity.get().hasPermission(PERMISSION_IS_SYSTEM);
+    private boolean isCallingPackageSelf() {
+        return mCallingIdentity.get().hasPermission(PERMISSION_IS_SELF);
     }
 
     @Deprecated
-    private boolean isCallingPackageBackup() {
-        return mCallingIdentity.get().hasPermission(PERMISSION_IS_BACKUP);
+    private boolean isCallingPackageShell() {
+        return mCallingIdentity.get().hasPermission(PERMISSION_IS_SHELL);
     }
 
     @Deprecated
-    private boolean isCallingPackageLegacyWrite() {
-        return mCallingIdentity.get().hasPermission(PERMISSION_IS_LEGACY_WRITE);
+    private boolean isCallingPackageManager() {
+        return mCallingIdentity.get().hasPermission(PERMISSION_IS_MANAGER);
+    }
+
+    @Deprecated
+    private boolean isCallingPackageDelegator() {
+        return mCallingIdentity.get().hasPermission(PERMISSION_IS_DELEGATOR);
     }
 
     @Deprecated
@@ -7550,11 +7599,11 @@
         return mCallingIdentity.get().hasPermission(PERMISSION_IS_LEGACY_READ);
     }
 
-    private boolean isCallingPackageExternalStorageManager() {
-        return mCallingIdentity.get().hasPermission(PERMISSION_MANAGE_EXTERNAL_STORAGE);
+    @Deprecated
+    private boolean isCallingPackageLegacyWrite() {
+        return mCallingIdentity.get().hasPermission(PERMISSION_IS_LEGACY_WRITE);
     }
 
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         writer.println("mThumbSize=" + mThumbSize);
diff --git a/src/com/android/providers/media/util/PermissionUtils.java b/src/com/android/providers/media/util/PermissionUtils.java
index bcf37be..fc10b78 100644
--- a/src/com/android/providers/media/util/PermissionUtils.java
+++ b/src/com/android/providers/media/util/PermissionUtils.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.BACKUP;
 import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
@@ -31,10 +32,9 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.app.AppOpsManager;
+import android.app.DownloadManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.provider.MediaStore;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -48,8 +48,6 @@
     // handle obscure cases like when an app targets Q but was installed on
     // a device that was originally running on P before being upgraded to Q.
 
-    private static volatile int sLegacyMediaProviderUid = -1;
-
     private static ThreadLocal<String> sOpDescription = new ThreadLocal<>();
 
     public static void setOpDescription(@Nullable String description) {
@@ -58,20 +56,25 @@
 
     public static void clearOpDescription() { sOpDescription.set(null); }
 
-    public static boolean checkPermissionSystem(
-            @NonNull Context context, int pid, int uid, String packageName) {
-        // Apps sharing legacy MediaProvider's uid like DownloadProvider and MTP are treated as
-        // system.
-        return uid == android.os.Process.SYSTEM_UID || uid == android.os.Process.myUid()
-                || uid == android.os.Process.SHELL_UID || uid == android.os.Process.ROOT_UID
-                || isLegacyMediaProvider(context, uid);
+    public static boolean checkPermissionSelf(@NonNull Context context, int pid, int uid) {
+        return android.os.Process.myUid() == uid;
     }
 
-    public static boolean checkPermissionBackup(@NonNull Context context, int pid, int uid) {
-        return context.checkPermission(BACKUP, pid, uid) == PERMISSION_GRANTED;
+    public static boolean checkPermissionShell(@NonNull Context context, int pid, int uid) {
+        switch (uid) {
+            case android.os.Process.ROOT_UID:
+            case android.os.Process.SHELL_UID:
+                return true;
+            default:
+                return false;
+        }
     }
 
-    public static boolean checkPermissionManageExternalStorage(@NonNull Context context, int pid,
+    /**
+     * Check if the given package has been granted the "file manager" role on
+     * the device, which should grant them certain broader access.
+     */
+    public static boolean checkPermissionManager(@NonNull Context context, int pid,
             int uid, @NonNull String packageName, @Nullable String attributionTag) {
         if (checkPermissionForDataDelivery(context, MANAGE_EXTERNAL_STORAGE, pid, uid,
                 packageName, attributionTag,
@@ -82,6 +85,23 @@
         return checkNoIsolatedStorageGranted(context, uid, packageName, attributionTag);
     }
 
+    /**
+     * Check if the given package has the ability to "delegate" the ownership of
+     * media items that they own to other apps, typically when they've finished
+     * performing operations on behalf of those apps.
+     * <p>
+     * One use-case for this is backup/restore apps, where the app restoring the
+     * content needs to shift the ownership back to the app that originally
+     * owned that media.
+     * <p>
+     * Another use-case is {@link DownloadManager}, which shifts ownership of
+     * finished downloads to the app that originally requested them.
+     */
+    public static boolean checkPermissionDelegator(@NonNull Context context, int pid, int uid) {
+        return (context.checkPermission(BACKUP, pid, uid) == PERMISSION_GRANTED)
+                || (context.checkPermission(UPDATE_DEVICE_STATS, pid, uid) == PERMISSION_GRANTED);
+    }
+
     public static boolean checkPermissionWriteStorage(@NonNull Context context, int pid, int uid,
             @NonNull String packageName, @Nullable String attributionTag) {
         return checkPermissionForDataDelivery(context, WRITE_EXTERNAL_STORAGE, pid, uid,
@@ -227,21 +247,6 @@
         }
     }
 
-    private static boolean isLegacyMediaProvider(Context context, int uid) {
-        if (sLegacyMediaProviderUid == -1) {
-            // Uid stays constant while legacy Media Provider stays installed. Cache legacy
-            // MediaProvider's uid for the first time.
-            ProviderInfo pi = context.getPackageManager()
-                    .resolveContentProvider(MediaStore.AUTHORITY_LEGACY, 0);
-            if (pi == null) {
-                return false;
-            }
-
-            sLegacyMediaProviderUid = pi.applicationInfo.uid;
-        }
-        return (uid == sLegacyMediaProviderUid);
-    }
-
     /**
      * Checks whether a given package in a UID and PID has a given permission
      * and whether the app op that corresponds to this permission is allowed.
diff --git a/tests/client/src/com/android/providers/media/client/DelegatorTest.java b/tests/client/src/com/android/providers/media/client/DelegatorTest.java
new file mode 100644
index 0000000..042223c
--- /dev/null
+++ b/tests/client/src/com/android/providers/media/client/DelegatorTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media.client;
+
+import static com.android.providers.media.client.LegacyProviderMigrationTest.executeShellCommand;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Verify media ownership delegation behaviors for an app holding
+ * {@code UPDATE_DEVICE_STATS} permission.
+ */
+@RunWith(AndroidJUnit4.class)
+public class DelegatorTest {
+    private static final String TAG = "DelegatorTest";
+
+    /**
+     * To confirm behaviors, we need to pick an app installed on all devices
+     * which has no permissions, and the best candidate is the "Easter Egg" app.
+     */
+    private static final String PERMISSIONLESS_APP = "com.android.egg";
+
+    private ContentResolver mResolver;
+
+    private Uri mExternalAudio = MediaStore.Audio.Media
+            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
+
+    @Before
+    public void setUp() throws Exception {
+        mResolver = InstrumentationRegistry.getTargetContext().getContentResolver();
+    }
+
+    /**
+     * Delegation allows us to push ownership of items we own to someone else.
+     */
+    @Test
+    public void testPushAllowed() throws Exception {
+        final Uri uri = createAudio();
+
+        assertEquals(InstrumentationRegistry.getTargetContext().getPackageName(), getOwner(uri));
+
+        final ContentValues values = new ContentValues();
+        values.put(MediaColumns.OWNER_PACKAGE_NAME, PERMISSIONLESS_APP);
+        mResolver.update(uri, values, null);
+
+        assertEquals(PERMISSIONLESS_APP, getOwner(uri));
+    }
+
+    /**
+     * Delegation allows us to push orphaned items to someone else.
+     */
+    @Test
+    public void testOrphanedAllowed() throws Exception {
+        final Uri uri = createAudio();
+        clearOwner(uri);
+
+        assertEquals(null, getOwner(uri));
+
+        final ContentValues values = new ContentValues();
+        values.put(MediaColumns.OWNER_PACKAGE_NAME, PERMISSIONLESS_APP);
+        mResolver.update(uri, values, null);
+
+        assertEquals(PERMISSIONLESS_APP, getOwner(uri));
+    }
+
+    /**
+     * However, attempting to steal items belonging to someone else is blocked.
+     */
+    @Test
+    public void testPullBlocked() throws Exception {
+        final Uri uri = createAudio();
+        setOwner(uri, PERMISSIONLESS_APP);
+
+        assertEquals(PERMISSIONLESS_APP, getOwner(uri));
+
+        final ContentValues values = new ContentValues();
+        values.put(MediaColumns.OWNER_PACKAGE_NAME,
+                InstrumentationRegistry.getTargetContext().getPackageName());
+        mResolver.update(uri, values, null);
+
+        assertEquals(PERMISSIONLESS_APP, getOwner(uri));
+    }
+
+    private Uri createAudio() throws IOException {
+        final ContentValues values = new ContentValues();
+        values.put(MediaColumns.DISPLAY_NAME, "Song " + System.nanoTime());
+        values.put(MediaColumns.MIME_TYPE, "audio/mpeg");
+
+        final Uri uri = mResolver.insert(mExternalAudio, values);
+        try (OutputStream out = mResolver.openOutputStream(uri)) {
+        }
+        return uri;
+    }
+
+    private String getOwner(Uri uri) throws Exception {
+        try (Cursor cursor = mResolver.query(uri,
+                new String[] { MediaColumns.OWNER_PACKAGE_NAME }, null, null)) {
+            assertTrue(cursor.moveToFirst());
+            return cursor.getString(0);
+        }
+    }
+
+    public static void setOwner(Uri uri, String packageName) throws Exception {
+        executeShellCommand("content update"
+                + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
+                + " --uri " + uri
+                + " --bind owner_package_name:s:" + packageName,
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
+    }
+
+    public static void clearOwner(Uri uri) throws Exception {
+        executeShellCommand("content update"
+                + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
+                + " --uri " + uri
+                + " --bind owner_package_name:n:",
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
+    }
+}
diff --git a/tests/src/com/android/providers/media/LocalCallingIdentityTest.java b/tests/src/com/android/providers/media/LocalCallingIdentityTest.java
index 64411d3..e30ed92 100644
--- a/tests/src/com/android/providers/media/LocalCallingIdentityTest.java
+++ b/tests/src/com/android/providers/media/LocalCallingIdentityTest.java
@@ -67,19 +67,22 @@
         assertEquals(Arrays.asList(pm.getPackagesForUid(android.os.Process.myUid())),
                 Arrays.asList(ident.getSharedPackageNames()));
 
-        assertTrue(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_SYSTEM));
-        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_LEGACY_WRITE));
+        assertTrue(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_SELF));
+        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_SHELL));
+        assertTrue(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_MANAGER));
+        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_DELEGATOR));
+
         assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_REDACTION_NEEDED));
+        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_LEGACY_GRANTED));
+        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_LEGACY_READ));
+        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_LEGACY_WRITE));
+
         assertTrue(ident.hasPermission(LocalCallingIdentity.PERMISSION_READ_AUDIO));
         assertTrue(ident.hasPermission(LocalCallingIdentity.PERMISSION_READ_VIDEO));
         assertTrue(ident.hasPermission(LocalCallingIdentity.PERMISSION_READ_IMAGES));
         assertTrue(ident.hasPermission(LocalCallingIdentity.PERMISSION_WRITE_AUDIO));
         assertTrue(ident.hasPermission(LocalCallingIdentity.PERMISSION_WRITE_VIDEO));
         assertTrue(ident.hasPermission(LocalCallingIdentity.PERMISSION_WRITE_IMAGES));
-        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_LEGACY_READ));
-        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_LEGACY_GRANTED));
-        assertTrue(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_BACKUP));
-        assertTrue(ident.hasPermission(LocalCallingIdentity.PERMISSION_MANAGE_EXTERNAL_STORAGE));
     }
 
     @Test
@@ -95,18 +98,21 @@
         assertEquals(Arrays.asList(MediaProviderTest.PERMISSIONLESS_APP),
                 Arrays.asList(ident.getSharedPackageNames()));
 
-        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_SYSTEM));
-        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_LEGACY_WRITE));
+        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_SELF));
+        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_SHELL));
+        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_MANAGER));
+        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_DELEGATOR));
+
         assertTrue(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_REDACTION_NEEDED));
+        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_LEGACY_GRANTED));
+        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_LEGACY_READ));
+        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_LEGACY_WRITE));
+
         assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_READ_AUDIO));
         assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_READ_VIDEO));
         assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_READ_IMAGES));
         assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_WRITE_AUDIO));
         assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_WRITE_VIDEO));
         assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_WRITE_IMAGES));
-        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_LEGACY_READ));
-        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_LEGACY_GRANTED));
-        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_IS_BACKUP));
-        assertFalse(ident.hasPermission(LocalCallingIdentity.PERMISSION_MANAGE_EXTERNAL_STORAGE));
     }
 }
diff --git a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
index 2cf1cdb..1eba379 100644
--- a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
@@ -17,13 +17,14 @@
 package com.android.providers.media.util;
 
 import static com.android.providers.media.util.PermissionUtils.checkNoIsolatedStorageGranted;
-import static com.android.providers.media.util.PermissionUtils.checkPermissionBackup;
-import static com.android.providers.media.util.PermissionUtils.checkPermissionManageExternalStorage;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionDelegator;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionManager;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadAudio;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadImages;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadStorage;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadVideo;
-import static com.android.providers.media.util.PermissionUtils.checkPermissionSystem;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionSelf;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionShell;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteAudio;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteImages;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteStorage;
@@ -58,9 +59,10 @@
         final int uid = android.os.Process.myUid();
         final String packageName = context.getPackageName();
 
-        assertTrue(checkPermissionSystem(context, pid, uid, packageName));
-        assertFalse(checkPermissionBackup(context, pid, uid));
-        assertFalse(checkPermissionManageExternalStorage(context, pid, uid, packageName, null));
+        assertTrue(checkPermissionSelf(context, pid, uid));
+        assertFalse(checkPermissionShell(context, pid, uid));
+        assertFalse(checkPermissionManager(context, pid, uid, packageName, null));
+        assertFalse(checkPermissionDelegator(context, pid, uid));
 
         assertTrue(checkPermissionReadStorage(context, pid, uid, packageName, null));
         assertTrue(checkPermissionWriteStorage(context, pid, uid, packageName, null));