Merge "Test that _modifier defaults to MODIFIER_MEDIA_SCAN after db upgrade" into sc-dev
diff --git a/Android.bp b/Android.bp
index fa33f8f..405f33c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -7,6 +7,7 @@
         "androidx.appcompat_appcompat",
         "androidx.core_core",
         "guava",
+        "modules-utils-build",
     ],
 
     libs: [
diff --git a/apex/framework/api/current.txt b/apex/framework/api/current.txt
index 623780a..0426cb0 100644
--- a/apex/framework/api/current.txt
+++ b/apex/framework/api/current.txt
@@ -12,6 +12,7 @@
     method public static long getGeneration(@NonNull android.content.Context, @NonNull String);
     method public static android.net.Uri getMediaScannerUri();
     method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri);
+    method @NonNull public static android.os.ParcelFileDescriptor getOriginalMediaFormatFileDescriptor(@NonNull android.content.Context, @NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
     method @NonNull public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
     method public static boolean getRequireOriginal(@NonNull android.net.Uri);
     method @NonNull public static String getVersion(@NonNull android.content.Context);
@@ -34,6 +35,7 @@
     field public static final String EXTRA_MEDIA_ALBUM = "android.intent.extra.album";
     field public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist";
     field public static final String EXTRA_MEDIA_CAPABILITIES = "android.provider.extra.MEDIA_CAPABILITIES";
+    field public static final String EXTRA_MEDIA_CAPABILITIES_UID = "android.provider.extra.MEDIA_CAPABILITIES_UID";
     field public static final String EXTRA_MEDIA_FOCUS = "android.intent.extra.focus";
     field public static final String EXTRA_MEDIA_GENRE = "android.intent.extra.genre";
     field public static final String EXTRA_MEDIA_PLAYLIST = "android.intent.extra.playlist";
@@ -135,6 +137,7 @@
     field public static final String IS_MUSIC = "is_music";
     field public static final String IS_NOTIFICATION = "is_notification";
     field public static final String IS_PODCAST = "is_podcast";
+    field public static final String IS_RECORDING = "is_recording";
     field public static final String IS_RINGTONE = "is_ringtone";
     field @Deprecated public static final String TITLE_KEY = "title_key";
     field public static final String TITLE_RESOURCE_URI = "title_resource_uri";
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index 2d6df79..4a12ce1 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -48,12 +48,14 @@
 import android.media.ExifInterface;
 import android.media.MediaFormat;
 import android.media.MediaMetadataRetriever;
+import android.media.MediaPlayer;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Environment;
 import android.os.OperationCanceledException;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
@@ -194,6 +196,10 @@
     public static final String FINISH_LEGACY_MIGRATION_CALL = "finish_legacy_migration";
 
     /** {@hide} */
+    public static final String GET_ORIGINAL_MEDIA_FORMAT_FILE_DESCRIPTOR_CALL =
+            "get_original_media_format_file_descriptor";
+
+    /** {@hide} */
     @Deprecated
     public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
             "com.android.externalstorage.documents";
@@ -215,6 +221,9 @@
     /** {@hide} */
     public static final String EXTRA_RESULT = "result";
 
+    /** {@hide} */
+    public static final String EXTRA_FILE_DESCRIPTOR = "file_descriptor";
+
     /**
      * This is for internal use by the media scanner only.
      * Name of the (optional) Uri parameter that determines whether to skip deleting
@@ -603,7 +612,7 @@
      */
     public final static String EXTRA_OUTPUT = "output";
 
-    /*
+    /**
      * Specify that the caller wants to receive the original media format without transcoding.
      *
      * <b>Caution: using this flag can cause app
@@ -626,6 +635,7 @@
      * @see ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)
      * @see ContentResolver#openTypedAssetFile(Uri, String, Bundle, CancellationSignal)
      * @see #setRequireOriginal(Uri)
+     * @see MediaStore#getOriginalMediaFormatFileDescriptor(Context, ParcelFileDescriptor)
      */
     public final static String EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT =
             "android.provider.extra.ACCEPT_ORIGINAL_MEDIA_FORMAT";
@@ -650,6 +660,17 @@
             "android.provider.extra.MEDIA_CAPABILITIES";
 
     /**
+     * Specify the UID of the app that should be used to determine supported media capabilities
+     * while opening a media.
+     *
+     * If this specified UID is found to be capable of handling the original media file format, the
+     * app will receive the original file, otherwise, the file will get transcoded to a default
+     * format supported by the specified UID.
+     */
+    public static final String EXTRA_MEDIA_CAPABILITIES_UID =
+            "android.provider.extra.MEDIA_CAPABILITIES_UID";
+
+    /**
       * The string that is used when a media attribute is not known. For example,
       * if an audio file does not have any meta data, the artist and album columns
       * will be set to this value.
@@ -838,6 +859,30 @@
     }
 
     /**
+     * Returns {@link ParcelFileDescriptor} representing the original media file format for
+     * {@code fileDescriptor}.
+     *
+     * <p>Media files may get transcoded based on an application's media capabilities requirements.
+     * However, in various cases, when the application needs access to the original media file, or
+     * doesn't attempt to parse the actual byte contents of media files, such as playback using
+     * {@link MediaPlayer} or for off-device backup, this method can be useful.
+     *
+     * @throws IOException if the given {@link ParcelFileDescriptor} could not be converted
+     *
+     * @see MediaStore#EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT
+     */
+    public static @NonNull ParcelFileDescriptor getOriginalMediaFormatFileDescriptor(
+            @NonNull Context context,
+            @NonNull ParcelFileDescriptor fileDescriptor) throws IOException {
+        Bundle input = new Bundle();
+        input.putParcelable(EXTRA_FILE_DESCRIPTOR, fileDescriptor);
+
+        Bundle output = context.getContentResolver().call(AUTHORITY,
+                GET_ORIGINAL_MEDIA_FORMAT_FILE_DESCRIPTOR_CALL, null, input);
+        return output.getParcelable(EXTRA_FILE_DESCRIPTOR);
+    }
+
+    /**
      * Rewrite the given {@link Uri} to point at
      * {@link MediaStore#AUTHORITY_LEGACY}.
      *
@@ -2708,6 +2753,12 @@
             public static final String IS_AUDIOBOOK = "is_audiobook";
 
             /**
+             * Non-zero if the audio file is a recording
+             */
+            @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
+            public static final String IS_RECORDING = "is_recording";
+
+            /**
              * The id of the genre the audio file is from, if any
              */
             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
diff --git a/legacy/Android.bp b/legacy/Android.bp
index 203ee5a..89ef09c 100644
--- a/legacy/Android.bp
+++ b/legacy/Android.bp
@@ -7,6 +7,7 @@
         "androidx.appcompat_appcompat",
         "androidx.core_core",
         "guava",
+        "modules-utils-build",
     ],
 
     libs: ["app-compat-annotations"],
@@ -17,7 +18,7 @@
         ":mediaprovider-database-sources",
     ],
 
+    platform_apis: true,
     certificate: "media",
     privileged: true,
-    sdk_version: "system_current",
 }
diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java
index 5a21531..6064d18 100644
--- a/src/com/android/providers/media/DatabaseHelper.java
+++ b/src/com/android/providers/media/DatabaseHelper.java
@@ -60,6 +60,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.providers.media.playlist.Playlist;
 import com.android.providers.media.util.BackgroundThread;
 import com.android.providers.media.util.DatabaseUtils;
@@ -813,7 +814,7 @@
                 + "scene_capture_type INTEGER DEFAULT NULL, generation_added INTEGER DEFAULT 0,"
                 + "generation_modified INTEGER DEFAULT 0, xmp BLOB DEFAULT NULL,"
                 + "_transcode_status INTEGER DEFAULT 0, _video_codec_type TEXT DEFAULT NULL,"
-                + "_modifier INTEGER DEFAULT 0)");
+                + "_modifier INTEGER DEFAULT 0, is_recording INTEGER DEFAULT 0)");
 
         db.execSQL("CREATE TABLE log (time DATETIME, message TEXT)");
         if (!mInternal) {
@@ -1370,6 +1371,14 @@
         db.execSQL("ALTER TABLE files ADD COLUMN is_audiobook INTEGER DEFAULT 0;");
     }
 
+    private static void updateAddRecording(SQLiteDatabase db, boolean internal) {
+        db.execSQL("ALTER TABLE files ADD COLUMN is_recording INTEGER DEFAULT 0;");
+        if (SdkLevel.isAtLeastS()) {
+            // We add the column is_recording, rescan all music files
+            db.execSQL("UPDATE files SET date_modified=0 WHERE is_music=1;");
+        }
+    }
+
     private static void updateClearLocation(SQLiteDatabase db, boolean internal) {
         db.execSQL("UPDATE files SET latitude=NULL, longitude=NULL;");
     }
@@ -1509,6 +1518,8 @@
 
     private static void updateAddModifier(SQLiteDatabase db, boolean internal) {
         db.execSQL("ALTER TABLE files ADD COLUMN _modifier INTEGER DEFAULT 0;");
+        // For existing files, set default value as _MODIFIER_MEDIA_SCAN
+        db.execSQL("UPDATE files SET _modifier=3;");
     }
 
     private static void recomputeDataValues(SQLiteDatabase db, boolean internal) {
@@ -1573,7 +1584,7 @@
     static final int VERSION_R = 1115;
     // Leave some gaps in database version tagging to allow R schema changes
     // to go independent of S schema changes.
-    static final int VERSION_S = 1204;
+    static final int VERSION_S = 1205;
     static final int VERSION_LATEST = VERSION_S;
 
     /**
@@ -1734,6 +1745,9 @@
             if (fromVersion < 1204) {
                 // Empty version bump to ensure views are recreated
             }
+            if (fromVersion < 1205) {
+                updateAddRecording(db, internal);
+            }
 
             // If this is the legacy database, it's not worth recomputing data
             // values locally, since they'll be recomputed after the migration
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index 92150f6..7d5395a 100644
--- a/src/com/android/providers/media/LocalCallingIdentity.java
+++ b/src/com/android/providers/media/LocalCallingIdentity.java
@@ -328,7 +328,7 @@
             return true;
         }
 
-        return checkIsLegacyStorageGranted(context, uid, getPackageName());
+        return checkIsLegacyStorageGranted(context, uid, getPackageName(), attributionTag);
     }
 
     private boolean isScopedStorageEnforced(boolean defaultScopedStorage,
diff --git a/src/com/android/providers/media/MediaDocumentsProvider.java b/src/com/android/providers/media/MediaDocumentsProvider.java
index 222b9ea..c450f9c 100644
--- a/src/com/android/providers/media/MediaDocumentsProvider.java
+++ b/src/com/android/providers/media/MediaDocumentsProvider.java
@@ -71,6 +71,7 @@
 import com.android.providers.media.util.FileUtils;
 
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -1018,6 +1019,7 @@
             throws FileNotFoundException {
         enforceShellRestrictions();
         final Uri target = getUriForDocumentId(docId);
+        final int callingUid = Binder.getCallingUid();
 
         if (!"r".equals(mode)) {
             throw new IllegalArgumentException("Media is read-only");
@@ -1026,12 +1028,27 @@
         // Delegate to real provider
         final long token = Binder.clearCallingIdentity();
         try {
-            return getContext().getContentResolver().openFileDescriptor(target, mode);
+            return openFileForRead(target, callingUid);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
+    public ParcelFileDescriptor openFileForRead(final Uri target, final int callingUid)
+            throws FileNotFoundException {
+        final Bundle opts = new Bundle();
+        opts.putInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID, callingUid);
+
+        AssetFileDescriptor afd =
+                getContext().getContentResolver().openTypedAssetFileDescriptor(target, "*/*",
+                        opts);
+        if (afd == null) {
+            return null;
+        }
+
+        return afd.getParcelFileDescriptor();
+    }
+
     @Override
     public AssetFileDescriptor openDocumentThumbnail(
             String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index e93787e..a7839aa 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -177,6 +177,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.providers.media.DatabaseHelper.OnFilesChangeListener;
 import com.android.providers.media.DatabaseHelper.OnLegacyMigrationListener;
 import com.android.providers.media.fuse.ExternalStorageServiceImpl;
@@ -263,6 +264,7 @@
     private static final String DIRECTORY_DCIM_LOWER_CASE = "dcim";
     private static final String DIRECTORY_DOCUMENTS_LOWER_CASE = "documents";
     private static final String DIRECTORY_AUDIOBOOKS_LOWER_CASE = "audiobooks";
+    private static final String DIRECTORY_RECORDINGS_LOWER_CASE = "recordings";
     private static final String DIRECTORY_ANDROID_LOWER_CASE = "android";
 
     private static final String DIRECTORY_MEDIA = "media";
@@ -1371,7 +1373,7 @@
     public boolean transformForFuse(String src, String dst, int transforms, int transformsReason,
             int uid) {
         if ((transforms & FLAG_TRANSFORM_TRANSCODING) != 0) {
-            if (mTranscodeHelper.isTranscodeFileCached(uid, src, dst)) {
+            if (mTranscodeHelper.isTranscodeFileCached(src, dst)) {
                 Log.d(TAG, "Using transcode cache for " + src);
                 return true;
             }
@@ -1932,16 +1934,25 @@
         return updateDatabaseForFuseRename(helper, oldPath, newPath, values, Bundle.EMPTY);
     }
 
+    private boolean updateDatabaseForFuseRename(@NonNull DatabaseHelper helper,
+            @NonNull String oldPath, @NonNull String newPath, @NonNull ContentValues values,
+            @NonNull Bundle qbExtras) {
+        return updateDatabaseForFuseRename(helper, oldPath, newPath, values, qbExtras,
+                FileUtils.getContentUriForPath(oldPath));
+    }
+
     /**
      * Updates database entry for given {@code path} with {@code values}
      */
     private boolean updateDatabaseForFuseRename(@NonNull DatabaseHelper helper,
             @NonNull String oldPath, @NonNull String newPath, @NonNull ContentValues values,
-            @NonNull Bundle qbExtras) {
-        final Uri uriOldPath = FileUtils.getContentUriForPath(oldPath);
+            @NonNull Bundle qbExtras, Uri uriOldPath) {
         boolean allowHidden = isCallingPackageAllowedHidden();
         final SQLiteQueryBuilder qbForUpdate = getQueryBuilder(TYPE_UPDATE,
                 matchUri(uriOldPath, allowHidden), uriOldPath, qbExtras, null);
+        if (values.containsKey(FileColumns._MODIFIER)) {
+            qbForUpdate.allowColumn(FileColumns._MODIFIER);
+        }
         final String selection = MediaColumns.DATA + " =? ";
         int count = 0;
         boolean retryUpdateWithReplace = false;
@@ -1976,7 +1987,7 @@
      * Gets {@link ContentValues} for updating database entry to {@code path}.
      */
     private ContentValues getContentValuesForFuseRename(String path, String newMimeType,
-            boolean wasHidden, boolean isHidden) {
+            boolean wasHidden, boolean isHidden, boolean isSameMimeType) {
         ContentValues values = new ContentValues();
         values.put(MediaColumns.MIME_TYPE, newMimeType);
         values.put(MediaColumns.DATA, path);
@@ -1986,15 +1997,15 @@
         } else {
             int mediaType = MimeUtils.resolveMediaType(newMimeType);
             values.put(FileColumns.MEDIA_TYPE, mediaType);
-            if (wasHidden) {
-                // Set this as pending so that apps can scan the file to update the metadata.
-                // Otherwise, scan will skip scanning this file because rename() doesn't change
-                // lastModifiedTime and scan assumes there is no change in the file.
-                // This should be safe because, in Q, apps had to insert new db row or scan the file
-                // to insert this file to database.
-                values.put(FileColumns.IS_PENDING, 1);
-            }
         }
+
+        if ((!isHidden && wasHidden) || !isSameMimeType) {
+            // Set the modifier as MODIFIER_FUSE so that apps can scan the file to update the
+            // metadata. Otherwise, scan will skip scanning this file because rename() doesn't
+            // change lastModifiedTime and scan assumes there is no change in the file.
+            values.put(FileColumns._MODIFIER, FileColumns._MODIFIER_FUSE);
+        }
+
         final boolean allowHidden = isCallingPackageAllowedHidden();
         if (!newMimeType.equalsIgnoreCase("null") &&
                 matchUri(getContentUriForFile(path, newMimeType), allowHidden) == AUDIO_MEDIA) {
@@ -2184,7 +2195,8 @@
                 final String newFilePath = newPath + "/" + filePath;
                 final String mimeType = MimeUtils.resolveMimeType(new File(newFilePath));
                 if(!updateDatabaseForFuseRename(helper, oldPath + "/" + filePath, newFilePath,
-                        getContentValuesForFuseRename(newFilePath, mimeType, wasHidden, isHidden),
+                        getContentValuesForFuseRename(newFilePath, mimeType, wasHidden, isHidden,
+                                /* isSameMimeType */ true),
                         qbExtras)) {
                     Log.e(TAG, "Calling package doesn't have write permission to rename file.");
                     return OsConstants.EPERM;
@@ -2264,11 +2276,19 @@
         helper.beginTransaction();
         try {
             final String newMimeType = MimeUtils.resolveMimeType(new File(newPath));
-            if (!updateDatabaseForFuseRename(helper, oldPath, newPath,
-                    getContentValuesForFuseRename(newPath, newMimeType, wasHidden, isHidden))) {
+            final String oldMimeType = MimeUtils.resolveMimeType(new File(oldPath));
+            final boolean isSameMimeType = newMimeType.equalsIgnoreCase(oldMimeType);
+            final ContentValues contentValues = getContentValuesForFuseRename(newPath, newMimeType,
+                    wasHidden, isHidden, isSameMimeType);
+
+            if (!updateDatabaseForFuseRename(helper, oldPath, newPath, contentValues)) {
                 if (!bypassRestrictions) {
-                    Log.e(TAG, "Calling package doesn't have write permission to rename file.");
-                    return OsConstants.EPERM;
+                    // Check for other URI format grants for oldPath only. Check right before
+                    // returning EPERM, to leave positive case performance unaffected.
+                    if (!renameWithOtherUriGrants(helper, oldPath, newPath, contentValues)) {
+                        Log.e(TAG, "Calling package doesn't have write permission to rename file.");
+                        return OsConstants.EPERM;
+                    }
                 } else if (!maybeRemoveOwnerPackageForFuseRename(helper, newPath)) {
                     Log.wtf(TAG, "Couldn't clear owner package name for " + newPath);
                     return OsConstants.EPERM;
@@ -2306,6 +2326,21 @@
     }
 
     /**
+     * Rename file by checking for other URI grants on oldPath
+     *
+     * We don't support replace scenario by checking for other URI grants on newPath (if it exists).
+     */
+    private boolean renameWithOtherUriGrants(DatabaseHelper helper, String oldPath, String newPath,
+            ContentValues contentValues) {
+        final Uri oldPathGrantedUri = getOtherUriGrantsForPath(oldPath, /* forWrite */ true);
+        if (oldPathGrantedUri == null) {
+            return false;
+        }
+        return updateDatabaseForFuseRename(helper, oldPath, newPath, contentValues, Bundle.EMPTY,
+                oldPathGrantedUri);
+    }
+
+    /**
      * Rename file/directory without imposing any restrictions.
      *
      * We don't impose any rename restrictions for apps that bypass scoped storage restrictions.
@@ -2741,13 +2776,24 @@
                 defaultMimeType = "audio/mpeg";
                 defaultMediaType = FileColumns.MEDIA_TYPE_AUDIO;
                 defaultPrimary = Environment.DIRECTORY_MUSIC;
-                allowedPrimary = Arrays.asList(
-                        Environment.DIRECTORY_ALARMS,
-                        Environment.DIRECTORY_AUDIOBOOKS,
-                        Environment.DIRECTORY_MUSIC,
-                        Environment.DIRECTORY_NOTIFICATIONS,
-                        Environment.DIRECTORY_PODCASTS,
-                        Environment.DIRECTORY_RINGTONES);
+                if (SdkLevel.isAtLeastS()) {
+                    allowedPrimary = Arrays.asList(
+                            Environment.DIRECTORY_ALARMS,
+                            Environment.DIRECTORY_AUDIOBOOKS,
+                            Environment.DIRECTORY_MUSIC,
+                            Environment.DIRECTORY_NOTIFICATIONS,
+                            Environment.DIRECTORY_PODCASTS,
+                            Environment.DIRECTORY_RECORDINGS,
+                            Environment.DIRECTORY_RINGTONES);
+                } else {
+                    allowedPrimary = Arrays.asList(
+                            Environment.DIRECTORY_ALARMS,
+                            Environment.DIRECTORY_AUDIOBOOKS,
+                            Environment.DIRECTORY_MUSIC,
+                            Environment.DIRECTORY_NOTIFICATIONS,
+                            Environment.DIRECTORY_PODCASTS,
+                            Environment.DIRECTORY_RINGTONES);
+                }
                 break;
             case VIDEO_MEDIA:
             case VIDEO_MEDIA_ID:
@@ -4947,7 +4993,7 @@
 
             // Check for other URI format grants for File API call only. Check right before
             // returning count = 0, to leave positive cases performance unaffected.
-            if (count == 0 && isFuseThread() && isFilePathSupportForMediaUris()) {
+            if (count == 0 && isFuseThread()) {
                 count += deleteWithOtherUriGrants(uri, helper, projection, userWhere, userWhereArgs,
                         extras);
             }
@@ -5195,6 +5241,22 @@
                 res.putParcelable(MediaStore.EXTRA_RESULT, pi);
                 return res;
             }
+            case MediaStore.GET_ORIGINAL_MEDIA_FORMAT_FILE_DESCRIPTOR_CALL: {
+                ParcelFileDescriptor inputPfd =
+                        extras.getParcelable(MediaStore.EXTRA_FILE_DESCRIPTOR);
+                try {
+                    File file = getFileFromFileDescriptor(inputPfd);
+                    FuseDaemon fuseDaemon = getFuseDaemonForFile(file);
+
+                    ParcelFileDescriptor outputPfd =
+                            fuseDaemon.getOriginalMediaFormatFileDescriptor(inputPfd);
+                    Bundle res = new Bundle();
+                    res.putParcelable(MediaStore.EXTRA_FILE_DESCRIPTOR, outputPfd);
+                    return res;
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
             default:
                 throw new UnsupportedOperationException("Unsupported call: " + method);
         }
@@ -5209,6 +5271,26 @@
     }
 
     /**
+     * Return the filesystem path of the real file on disk that is represented
+     * by the given {@link ParcelFileDescriptor}.
+     *
+     * Copied from {@link ParcelFileDescriptor#getFile}
+     */
+    private static File getFileFromFileDescriptor(ParcelFileDescriptor fileDescriptor)
+            throws IOException {
+        try {
+            final String path = Os.readlink("/proc/self/fd/" + fileDescriptor.getFd());
+            if (OsConstants.S_ISREG(Os.stat(path).st_mode)) {
+                return new File(path);
+            } else {
+                throw new IOException("Not a regular file: " + path);
+            }
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+    }
+
+    /**
      * Generate the {@link PendingIntent} for the given grant request. This
      * method also checks the incoming arguments for security purposes
      * before creating the privileged {@link PendingIntent}.
@@ -7349,19 +7431,20 @@
         try {
             if (isPrivatePackagePathNotAccessibleByCaller(path)) {
                 Log.e(TAG, "Can't open a file in another app's external directory!");
-                return new FileOpenResult(OsConstants.ENOENT, uid, new long[0]);
+                return new FileOpenResult(OsConstants.ENOENT, original_uid, new long[0]);
             }
 
             if (shouldBypassFuseRestrictions(forWrite, path)) {
                 isSuccess = true;
-                return new FileOpenResult(0 /* status */, uid,
+                return new FileOpenResult(0 /* status */, original_uid,
                         redact ? getRedactionRangesForFuse(path, ioPath, original_uid, uid, tid) :
                         new long[0]);
             }
             // Legacy apps that made is this far don't have the right storage permission and hence
             // are not allowed to access anything other than their external app directory
             if (isCallingPackageRequestingLegacy()) {
-                return new FileOpenResult(OsConstants.EACCES /* status */, uid, new long[0]);
+                return new FileOpenResult(OsConstants.EACCES /* status */, original_uid,
+                        new long[0]);
             }
 
             final Uri contentUri = FileUtils.getContentUriForPath(path);
@@ -7403,13 +7486,12 @@
             } catch (SecurityException e) {
                 // Check for other Uri formats only when the single uri check flow fails.
                 // Throw the previous exception if the multi-uri checks failed.
-                if (!(isFilePathSupportForMediaUris() &&
-                        getOtherUriGrantsForPath(path, mediaType, id, forWrite) != null)) {
+                if (getOtherUriGrantsForPath(path, mediaType, id, forWrite) == null) {
                     throw e;
                 }
             }
             isSuccess = true;
-            return new FileOpenResult(0 /* status */, uid,
+            return new FileOpenResult(0 /* status */, original_uid,
                     redact ? getRedactionRangesForFuse(path, ioPath, original_uid, uid, tid) :
                     new long[0]);
         } catch (IOException e) {
@@ -7418,10 +7500,10 @@
             // * getRedactionRangesForFuse couldn't fetch the redaction info correctly
             // In all of these cases, it means that app doesn't have access permission to the file.
             Log.e(TAG, "Couldn't find file: " + path, e);
-            return new FileOpenResult(OsConstants.EACCES /* status */, uid, new long[0]);
+            return new FileOpenResult(OsConstants.EACCES /* status */, original_uid, new long[0]);
         } catch (IllegalStateException | SecurityException e) {
             Log.e(TAG, "Permission to access file: " + path + " is denied");
-            return new FileOpenResult(OsConstants.EACCES /* status */, uid, new long[0]);
+            return new FileOpenResult(OsConstants.EACCES /* status */, original_uid, new long[0]);
         } finally {
             if (isSuccess && logTransformsMetrics) {
                 notifyTranscodeHelperOnFileOpen(path, ioPath, original_uid, transformsReason);
@@ -7430,6 +7512,25 @@
         }
     }
 
+    private @Nullable Uri getOtherUriGrantsForPath(String path, boolean forWrite) {
+        final Uri contentUri = FileUtils.getContentUriForPath(path);
+        final String[] projection = new String[]{
+                MediaColumns._ID,
+                FileColumns.MEDIA_TYPE};
+        final String selection = MediaColumns.DATA + "=?";
+        final String[] selectionArgs = new String[]{ path };
+        final long id;
+        final int mediaType;
+        try (final Cursor c = queryForSingleItemAsMediaProvider(contentUri, projection, selection,
+                selectionArgs, null)) {
+            id = c.getLong(0);
+            mediaType = c.getInt(1);
+            return getOtherUriGrantsForPath(path, mediaType, id, forWrite);
+        } catch (FileNotFoundException ignored) {
+        }
+        return null;
+    }
+
     private @Nullable Uri getOtherUriGrantsForPath(String path, int mediaType, long id,
             boolean forWrite) {
         Set<Uri> otherUris = new ArraySet<Uri>();
@@ -7458,17 +7559,6 @@
     }
 
     /**
-     * Feature flag to support File APIs for different formats of media-store URI grants like:
-     *   * content://media/external_primary/images/media/123
-     *   * content://media/external/images/media/123
-     *
-     *   Default value: false
-     */
-    private boolean isFilePathSupportForMediaUris() {
-        return SystemProperties.getBoolean("sys.filepathsupport.mediauri", false);
-    }
-
-    /**
      * Returns {@code true} if {@link #mCallingIdentity#getSharedPackages(String)} contains the
      * given package name, {@code false} otherwise.
      * <p> Assumes that {@code mCallingIdentity} has been properly set to reflect the calling
@@ -7510,6 +7600,7 @@
             case DIRECTORY_ALARMS_LOWER_CASE:
             case DIRECTORY_NOTIFICATIONS_LOWER_CASE:
             case DIRECTORY_AUDIOBOOKS_LOWER_CASE:
+            case DIRECTORY_RECORDINGS_LOWER_CASE:
                 uri = Audio.Media.getContentUri(volName);
                 break;
             case DIRECTORY_MUSIC_LOWER_CASE:
diff --git a/src/com/android/providers/media/TranscodeHelper.java b/src/com/android/providers/media/TranscodeHelper.java
index c830a5f..4377856 100644
--- a/src/com/android/providers/media/TranscodeHelper.java
+++ b/src/com/android/providers/media/TranscodeHelper.java
@@ -61,6 +61,7 @@
 import android.provider.MediaStore;
 import android.provider.MediaStore.Files.FileColumns;
 import android.provider.MediaStore.MediaColumns;
+import android.provider.MediaStore.Video.VideoColumns;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -382,6 +383,21 @@
         return transcodePath;
     }
 
+    private static int getMediaCapabilitiesUid(int uid, Bundle bundle) {
+        if (bundle == null) {
+            return uid;
+        }
+        int mediaCapabilitiesUid = bundle.getInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID);
+        if (mediaCapabilitiesUid >= Process.FIRST_APPLICATION_UID) {
+            logVerbose(
+                    "Media capabilities uid " + mediaCapabilitiesUid + ", passed for uid " + uid);
+            uid = mediaCapabilitiesUid;
+        } else {
+            logVerbose("Ignoring invalid Media capabilities uid " + mediaCapabilitiesUid);
+        }
+        return uid;
+    }
+
     // TODO(b/173491972): Generalize to consider other file/app media capabilities beyond hevc
     /**
      * @return 0 or >0 representing whether we should transcode or not.
@@ -404,6 +420,8 @@
             logVerbose("Transcode not enabled");
             return 0;
         }
+
+        uid = getMediaCapabilitiesUid(uid, bundle);
         logVerbose("Checking shouldTranscode for: " + path + ". Uid: " + uid);
 
         if (!supportsTranscode(path) || uid < Process.FIRST_APPLICATION_UID
@@ -422,6 +440,7 @@
 
         if (fileFlags == 0) {
             // Nothing to transcode
+            logVerbose("File is not HEVC");
             return 0;
         }
 
@@ -566,26 +585,39 @@
     }
 
     private int getFileFlags(String path) {
-        try (Cursor cursor = queryFileForTranscode(path,
-                        new String[]{FileColumns._VIDEO_CODEC_TYPE})) {
+        final String[] projection = new String[] {
+            FileColumns._VIDEO_CODEC_TYPE,
+            VideoColumns.COLOR_STANDARD,
+            VideoColumns.COLOR_TRANSFER
+        };
+
+        try (Cursor cursor = queryFileForTranscode(path, projection)) {
             if (cursor == null || !cursor.moveToNext()) {
                 logVerbose("Couldn't find database row");
                 return 0;
             }
 
+            int result = 0;
             if (isHevc(cursor.getString(0))) {
-                return FLAG_HEVC;
-            } else {
-                logVerbose("File is not HEVC");
-                return 0;
+                result |= FLAG_HEVC;
             }
+            if (isHdr10Plus(cursor.getInt(1), cursor.getInt(2))) {
+                result |= FLAG_HDR_10_PLUS;
+            }
+            return result;
         }
     }
 
-    private boolean isHevc(String mimeType) {
+    private static boolean isHevc(String mimeType) {
         return MediaFormat.MIMETYPE_VIDEO_HEVC.equalsIgnoreCase(mimeType);
     }
 
+    private static boolean isHdr10Plus(int colorStandard, int colorTransfer) {
+        return (colorStandard == MediaFormat.COLOR_STANDARD_BT2020) &&
+                (colorTransfer == MediaFormat.COLOR_TRANSFER_ST2084
+                        || colorTransfer == MediaFormat.COLOR_TRANSFER_HLG);
+    }
+
     public boolean supportsTranscode(String path) {
         File file = new File(path);
         String name = file.getName();
@@ -782,7 +814,7 @@
                                 c.getLong(2) /* video_duration */,
                                 c.getLong(3) /* capture_framerate */,
                                 -1 /* transcode_reason */);
-                    } else if (isTranscodeFileCached(uid, path, ioPath)) {
+                    } else if (isTranscodeFileCached(path, ioPath)) {
                             MediaProviderStatsLog.write(
                                     TRANSCODING_DATA,
                                     getNameForUid(uid) /* owner_package_name */,
@@ -801,7 +833,7 @@
         }
     }
 
-    public boolean isTranscodeFileCached(int uid, String path, String transcodePath) {
+    public boolean isTranscodeFileCached(String path, String transcodePath) {
         if (SystemProperties.getBoolean("sys.fuse.disable_transcode_cache", false)) {
             // Caching is disabled. Hence, delete the cached transcode file.
             return false;
diff --git a/src/com/android/providers/media/fuse/FuseDaemon.java b/src/com/android/providers/media/fuse/FuseDaemon.java
index 9a433c9..1ce6d04 100644
--- a/src/com/android/providers/media/fuse/FuseDaemon.java
+++ b/src/com/android/providers/media/fuse/FuseDaemon.java
@@ -151,6 +151,12 @@
         }
     }
 
+    public ParcelFileDescriptor getOriginalMediaFormatFileDescriptor(
+            ParcelFileDescriptor fileDescriptor) {
+        // TODO (b/170488060): Implement get original media file fd via native fuse.
+        throw new UnsupportedOperationException();
+    }
+
     private native long native_new(MediaProvider mediaProvider);
 
     // Takes ownership of the passed in file descriptor!
diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java
index f7fa001..c88fc20 100644
--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
+++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
@@ -90,6 +90,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.providers.media.util.DatabaseUtils;
 import com.android.providers.media.util.ExifUtils;
 import com.android.providers.media.util.FileUtils;
@@ -736,8 +737,8 @@
 
                     final boolean sameMetadata =
                             hasSameMetadata(attrs, realFile, isPendingFromFuse, c);
-                    if (isSame(
-                            sameMetadata, actualMimeType, actualMediaType, mimeType, mediaType)) {
+                    final boolean sameMediaType = actualMediaType == mediaType;
+                    if (sameMetadata && sameMediaType) {
                         if (LOGV) Log.v(TAG, "Skipping unchanged " + file);
                         return FileVisitResult.CONTINUE;
                     }
@@ -750,21 +751,6 @@
                         if (LOGV) Log.v(TAG, "Skipping unchanged video/audio " + file);
                         return FileVisitResult.CONTINUE;
                     }
-
-                    // "audio/mp4" mime types can come from various extensions (e.g. 3ga, m4a). We
-                    // want to avoid unnecessary scans of these files (it takes a long time for
-                    // many files, e.g. on phone reboot), so we check the mime type from the file's
-                    // metadata. We avoid always checking the file's metadata (and only do it for
-                    // this narrow case) since it involves expensive file operations.
-                    if (sameMetadata
-                            && (actualMediaType == mediaType)
-                            && "audio/mp4".equalsIgnoreCase(mimeType)) {
-                        actualMimeType = getAudioMimeTypeFromMetadata(realFile, actualMimeType);
-                        if (mimeType.equalsIgnoreCase(actualMimeType)) {
-                            if (LOGV) Log.v(TAG, "Skipping unchanged audio/mp4 " + file);
-                            return FileVisitResult.CONTINUE;
-                        }
-                    }
                 }
 
                 // Since we allow top-level mime type to be customised, we need to do this early
@@ -803,20 +789,6 @@
             return FileVisitResult.CONTINUE;
         }
 
-        private boolean isSame(
-                boolean hasSameMetadata,
-                String actualMimeType,
-                int actualMediaType,
-                String mimeType,
-                int mediaType) {
-            boolean sameMimeType =
-                    mimeType == null
-                            ? actualMimeType == null
-                            : mimeType.equalsIgnoreCase(actualMimeType);
-            boolean sameMediaType = (actualMediaType == mediaType);
-            return hasSameMetadata && sameMediaType && sameMimeType;
-        }
-
         private int mediaTypeFromMimeType(
                 File file, String mimeType, int defaultMediaType) {
             if (mimeType != null) {
@@ -862,25 +834,6 @@
             return defaultMimeType;
         }
 
-        /**
-         * Returns the mime type as read from the metadata of the given file or the given default
-         * value if we cannot read the metadata. We want to avoid calling this during sanning,
-         * since it involves expensive file operations.
-         */
-        private String getAudioMimeTypeFromMetadata(File file, String defaultMimeType) {
-            try (
-                    FileInputStream is = new FileInputStream(file);
-                    MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
-                mmr.setDataSource(is.getFD());
-                Optional<String> optionalMimeType =
-                        parseOptionalMimeType(
-                                defaultMimeType, mmr.extractMetadata(METADATA_KEY_MIMETYPE));
-                return optionalMimeType.orElse(defaultMimeType);
-            } catch (Exception e) {
-                return defaultMimeType;
-            }
-        }
-
         @Override
         public FileVisitResult visitFileFailed(Path file, IOException exc)
                 throws IOException {
@@ -1240,6 +1193,9 @@
         sAudioTypes.put(Environment.DIRECTORY_PODCASTS, AudioColumns.IS_PODCAST);
         sAudioTypes.put(Environment.DIRECTORY_AUDIOBOOKS, AudioColumns.IS_AUDIOBOOK);
         sAudioTypes.put(Environment.DIRECTORY_MUSIC, AudioColumns.IS_MUSIC);
+        if (SdkLevel.isAtLeastS()) {
+            sAudioTypes.put(Environment.DIRECTORY_RECORDINGS, AudioColumns.IS_RECORDING);
+        }
     }
 
     private static @NonNull ContentProviderOperation.Builder scanItemAudio(long existingId,
diff --git a/src/com/android/providers/media/util/FileUtils.java b/src/com/android/providers/media/util/FileUtils.java
index 0d6494b..7e79091 100644
--- a/src/com/android/providers/media/util/FileUtils.java
+++ b/src/com/android/providers/media/util/FileUtils.java
@@ -64,6 +64,8 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
@@ -932,19 +934,39 @@
             "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:obb)(/?.*)");
 
     @VisibleForTesting
-    public static final String[] DEFAULT_FOLDER_NAMES = {
-            Environment.DIRECTORY_MUSIC,
-            Environment.DIRECTORY_PODCASTS,
-            Environment.DIRECTORY_RINGTONES,
-            Environment.DIRECTORY_ALARMS,
-            Environment.DIRECTORY_NOTIFICATIONS,
-            Environment.DIRECTORY_PICTURES,
-            Environment.DIRECTORY_MOVIES,
-            Environment.DIRECTORY_DOWNLOADS,
-            Environment.DIRECTORY_DCIM,
-            Environment.DIRECTORY_DOCUMENTS,
-            Environment.DIRECTORY_AUDIOBOOKS,
-    };
+    public static final String[] DEFAULT_FOLDER_NAMES;
+    static {
+        if (SdkLevel.isAtLeastS()) {
+            DEFAULT_FOLDER_NAMES = new String[]{
+                    Environment.DIRECTORY_MUSIC,
+                    Environment.DIRECTORY_PODCASTS,
+                    Environment.DIRECTORY_RINGTONES,
+                    Environment.DIRECTORY_ALARMS,
+                    Environment.DIRECTORY_NOTIFICATIONS,
+                    Environment.DIRECTORY_PICTURES,
+                    Environment.DIRECTORY_MOVIES,
+                    Environment.DIRECTORY_DOWNLOADS,
+                    Environment.DIRECTORY_DCIM,
+                    Environment.DIRECTORY_DOCUMENTS,
+                    Environment.DIRECTORY_AUDIOBOOKS,
+                    Environment.DIRECTORY_RECORDINGS,
+            };
+        } else {
+            DEFAULT_FOLDER_NAMES = new String[]{
+                    Environment.DIRECTORY_MUSIC,
+                    Environment.DIRECTORY_PODCASTS,
+                    Environment.DIRECTORY_RINGTONES,
+                    Environment.DIRECTORY_ALARMS,
+                    Environment.DIRECTORY_NOTIFICATIONS,
+                    Environment.DIRECTORY_PICTURES,
+                    Environment.DIRECTORY_MOVIES,
+                    Environment.DIRECTORY_DOWNLOADS,
+                    Environment.DIRECTORY_DCIM,
+                    Environment.DIRECTORY_DOCUMENTS,
+                    Environment.DIRECTORY_AUDIOBOOKS,
+            };
+        }
+    }
 
     /**
      * Regex that matches paths for {@link MediaColumns#RELATIVE_PATH}
diff --git a/src/com/android/providers/media/util/PermissionUtils.java b/src/com/android/providers/media/util/PermissionUtils.java
index c52aa8b..d9765ac 100644
--- a/src/com/android/providers/media/util/PermissionUtils.java
+++ b/src/com/android/providers/media/util/PermissionUtils.java
@@ -78,13 +78,9 @@
      */
     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,
+        return checkPermissionForDataDelivery(context, MANAGE_EXTERNAL_STORAGE, pid, uid,
                 packageName, attributionTag,
-                generateAppOpMessage(packageName,sOpDescription.get()))) {
-            return true;
-        }
-        // Fallback to OPSTR_NO_ISOLATED_STORAGE app op.
-        return checkNoIsolatedStorageGranted(context, uid, packageName, attributionTag);
+                generateAppOpMessage(packageName,sOpDescription.get()));
     }
 
     /**
@@ -118,10 +114,14 @@
                 generateAppOpMessage(packageName,sOpDescription.get()));
     }
 
-    public static boolean checkIsLegacyStorageGranted(
-            @NonNull Context context, int uid, String packageName) {
-        return context.getSystemService(AppOpsManager.class)
-                .unsafeCheckOp(OPSTR_LEGACY_STORAGE, uid, packageName) == MODE_ALLOWED;
+    public static boolean checkIsLegacyStorageGranted(@NonNull Context context, int uid,
+            String packageName, @Nullable String attributionTag) {
+        if (context.getSystemService(AppOpsManager.class)
+                .unsafeCheckOp(OPSTR_LEGACY_STORAGE, uid, packageName) == MODE_ALLOWED) {
+            return true;
+        }
+        // Check OPSTR_NO_ISOLATED_STORAGE app op.
+        return checkNoIsolatedStorageGranted(context, uid, packageName, attributionTag);
     }
 
     public static boolean checkPermissionReadAudio(@NonNull Context context, int pid, int uid,
diff --git a/tests/Android.bp b/tests/Android.bp
index 25dd84a..ed92db2 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -38,6 +38,7 @@
         "androidx.test.rules",
         "guava",
         "mockito-target",
+        "modules-utils-build",
         "truth-prebuilt",
     ],
 
diff --git a/tests/src/com/android/providers/media/LocalCallingIdentityTest.java b/tests/src/com/android/providers/media/LocalCallingIdentityTest.java
index e30ed92..40e1175 100644
--- a/tests/src/com/android/providers/media/LocalCallingIdentityTest.java
+++ b/tests/src/com/android/providers/media/LocalCallingIdentityTest.java
@@ -29,6 +29,7 @@
 
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -86,6 +87,7 @@
     }
 
     @Test
+    @Ignore("b/179675679")
     public void testFromExternal() throws Exception {
         final Context context = InstrumentationRegistry.getContext();
         final PackageManager pm = context.getPackageManager();
diff --git a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
index e1312c2..d913307 100644
--- a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
+++ b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
@@ -777,6 +777,25 @@
         }
     }
 
+    @Test
+    public void testScan_audio_recording() throws Exception {
+        final File music = new File(mDir, "Recordings");
+        final File audio = new File(music, "audio.mp3");
+
+        music.mkdirs();
+        stage(R.raw.test_audio, audio);
+
+        mModern.scanFile(audio, REASON_UNKNOWN);
+
+        try (Cursor cursor = mIsolatedResolver
+                .query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, null)) {
+            assertEquals(1, cursor.getCount());
+            cursor.moveToFirst();
+            assertEquals(1, cursor.getInt(cursor.getColumnIndex(AudioColumns.IS_RECORDING)));
+            assertEquals(0, cursor.getInt(cursor.getColumnIndex(AudioColumns.IS_MUSIC)));
+        }
+    }
+
     /**
      * Verify a narrow exception where we allow an {@code mp4} video file on
      * disk to be indexed as an {@code m4a} audio file.
diff --git a/tests/transcode/src/com/android/providers/media/transcode/TranscodeTest.java b/tests/transcode/src/com/android/providers/media/transcode/TranscodeTest.java
index e095168..3df318b 100644
--- a/tests/transcode/src/com/android/providers/media/transcode/TranscodeTest.java
+++ b/tests/transcode/src/com/android/providers/media/transcode/TranscodeTest.java
@@ -32,10 +32,10 @@
 import android.media.ApplicationMediaCapabilities;
 import android.media.MediaFormat;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
+import android.os.SystemProperties;
 import android.provider.MediaStore;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -85,7 +85,7 @@
     @Before
     public void setUp() throws Exception {
         // TODO(b/171789917): Cuttlefish doesn't support transcoding yet
-        Assume.assumeFalse(Build.MODEL.contains("Cuttlefish"));
+        Assume.assumeFalse(SystemProperties.get("ro.product.vendor.model").contains("Cuttlefish"));
 
         TranscodeTestUtils.pollForExternalStorageState();
         TranscodeTestUtils.grantPermission(getContext().getPackageName(),
@@ -737,4 +737,94 @@
             modernFile.delete();
         }
     }
+
+    /**
+     * Tests that we transcode an HEVC file when a modern app passes the mediaCapabilitiesUid of a
+     * legacy app that cannot handle an HEVC file.
+     */
+    @Test
+    public void testOriginalCallingUid_modernAppPassLegacyAppUid()
+            throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        ParcelFileDescriptor pfdModernApp = null;
+        ParcelFileDescriptor pfdModernAppPassingLegacyUid = null;
+        try {
+            installAppWithStoragePermissions(TEST_APP_SLOW_MOTION);
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            // pfdModernApp is for original content (without transcoding) since this is a modern
+            // app.
+            pfdModernApp = open(modernFile, false);
+
+            // pfdModernAppPassingLegacyUid is for transcoded content since this modern app is
+            // passing the UID of a legacy app capable of handling HEVC files.
+            Bundle bundle = new Bundle();
+            bundle.putInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID,
+                    getContext().getPackageManager().getPackageUid(
+                            TEST_APP_SLOW_MOTION.getPackageName(), 0));
+            pfdModernAppPassingLegacyUid = open(uri, false, bundle);
+
+            assertTranscode(pfdModernApp, false);
+            assertTranscode(pfdModernAppPassingLegacyUid, true);
+
+            // pfdModernApp and pfdModernAppPassingLegacyUid should be different.
+            assertFileContent(modernFile, modernFile, pfdModernApp, pfdModernAppPassingLegacyUid,
+                    false);
+        } finally {
+            if (pfdModernApp != null) {
+                pfdModernApp.close();
+            }
+
+            if (pfdModernAppPassingLegacyUid != null) {
+                pfdModernAppPassingLegacyUid.close();
+            }
+            modernFile.delete();
+            uninstallApp(TEST_APP_SLOW_MOTION);
+        }
+    }
+
+    /**
+     * Tests that we don't transcode an HEVC file when a legacy app passes the mediaCapabilitiesUid
+     * of a modern app that can handle an HEVC file.
+     */
+    @Test
+    public void testOriginalCallingUid_legacyAppPassModernAppUid()
+            throws Exception {
+        File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+        ParcelFileDescriptor pfdLegacyApp = null;
+        ParcelFileDescriptor pfdLegacyAppPassingModernUid = null;
+        try {
+            installAppWithStoragePermissions(TEST_APP_HEVC);
+            Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+            // pfdLegacyApp is for transcoded content since this is a legacy app.
+            TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+            pfdLegacyApp = open(modernFile, false);
+
+            // pfdLegacyAppPassingModernUid is for original content (without transcoding) since this
+            // legacy app is passing the UID of a modern app capable of handling HEVC files.
+            Bundle bundle = new Bundle();
+            bundle.putInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID,
+                    getContext().getPackageManager().getPackageUid(TEST_APP_HEVC.getPackageName(),
+                            0));
+            pfdLegacyAppPassingModernUid = open(uri, false, bundle);
+
+            assertTranscode(pfdLegacyApp, true);
+            assertTranscode(pfdLegacyAppPassingModernUid, false);
+
+            // pfdLegacyApp and pfdLegacyAppPassingModernUid should be different.
+            assertFileContent(modernFile, modernFile, pfdLegacyApp, pfdLegacyAppPassingModernUid,
+                    false);
+        } finally {
+            if (pfdLegacyApp != null) {
+                pfdLegacyApp.close();
+            }
+
+            if (pfdLegacyAppPassingModernUid != null) {
+                pfdLegacyAppPassingModernUid.close();
+            }
+            modernFile.delete();
+            uninstallApp(TEST_APP_HEVC);
+        }
+    }
 }