Merge "Remove FuseTargetPreparer" into rvc-dev
diff --git a/Android.bp b/Android.bp
index 49863ed..f59369f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -71,6 +71,7 @@
         "src/com/android/providers/media/util/BackgroundThread.java",
         "src/com/android/providers/media/util/DatabaseUtils.java",
         "src/com/android/providers/media/util/FileUtils.java",
+        "src/com/android/providers/media/util/ForegroundThread.java",
         "src/com/android/providers/media/util/HandlerExecutor.java",
         "src/com/android/providers/media/util/Logging.java",
         "src/com/android/providers/media/util/MimeUtils.java",
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index 62fb282..ca32355 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -28,6 +28,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.annotation.WorkerThread;
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -99,10 +100,27 @@
     public static final @NonNull Uri AUTHORITY_URI =
             Uri.parse("content://" + AUTHORITY);
 
-    /** @hide */
+    /**
+     * The authority for a legacy instance of the media provider, before it was
+     * converted into a Mainline module. When initializing for the first time,
+     * the Mainline module will connect to this legacy instance to migrate
+     * important user settings, such as {@link BaseColumns#_ID},
+     * {@link MediaColumns#IS_FAVORITE}, and more.
+     * <p>
+     * The legacy instance is expected to meet the exact same API contract
+     * expressed here in {@link MediaStore}, to facilitate smooth data
+     * migrations. Interactions that would normally interact with
+     * {@link #AUTHORITY} can be redirected to work with the legacy instance
+     * using {@link #rewriteToLegacy(Uri)}.
+     *
+     * @hide
+     */
     @SystemApi
     public static final String AUTHORITY_LEGACY = "media_legacy";
-    /** @hide */
+    /**
+     * @see #AUTHORITY_LEGACY
+     * @hide
+     */
     @SystemApi
     public static final @NonNull Uri AUTHORITY_LEGACY_URI =
             Uri.parse("content://" + AUTHORITY_LEGACY);
@@ -163,6 +181,11 @@
     public static final String GET_GENERATION_CALL = "get_generation";
 
     /** {@hide} */
+    public static final String START_LEGACY_MIGRATION_CALL = "start_legacy_migration";
+    /** {@hide} */
+    public static final String FINISH_LEGACY_MIGRATION_CALL = "finish_legacy_migration";
+
+    /** {@hide} */
     @Deprecated
     public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
             "com.android.externalstorage.documents";
@@ -729,6 +752,7 @@
      * Rewrite the given {@link Uri} to point at
      * {@link MediaStore#AUTHORITY_LEGACY}.
      *
+     * @see #AUTHORITY_LEGACY
      * @hide
      */
     @SystemApi
@@ -736,6 +760,29 @@
         return uri.buildUpon().authority(MediaStore.AUTHORITY_LEGACY).build();
     }
 
+    /**
+     * Called by the Mainline module to signal to {@link #AUTHORITY_LEGACY} that
+     * data migration is starting.
+     *
+     * @hide
+     */
+    public static void startLegacyMigration(@NonNull ContentResolver resolver,
+            @NonNull String volumeName) {
+        resolver.call(AUTHORITY_LEGACY, START_LEGACY_MIGRATION_CALL, volumeName, null);
+    }
+
+    /**
+     * Called by the Mainline module to signal to {@link #AUTHORITY_LEGACY} that
+     * data migration is finished. The legacy provider may choose to perform
+     * clean-up operations at this point, such as deleting databases.
+     *
+     * @hide
+     */
+    public static void finishLegacyMigration(@NonNull ContentResolver resolver,
+            @NonNull String volumeName) {
+        resolver.call(AUTHORITY_LEGACY, FINISH_LEGACY_MIGRATION_CALL, volumeName, null);
+    }
+
     private static @NonNull PendingIntent createRequest(@NonNull ContentResolver resolver,
             @NonNull String method, @NonNull Collection<Uri> uris, @Nullable ContentValues values) {
         Objects.requireNonNull(resolver);
@@ -761,7 +808,8 @@
      * call {@link Activity#startIntentSenderForResult} with
      * {@link PendingIntent#getIntentSender()}. You can then determine if the
      * user granted your request by testing for {@link Activity#RESULT_OK} in
-     * {@link Activity#onActivityResult}.
+     * {@link Activity#onActivityResult}. The requested operation will have
+     * completely finished before this activity result is delivered.
      * <p>
      * Permissions granted through this mechanism are tied to the lifecycle of
      * the {@link Activity} that requests them. If you need to retain
@@ -781,6 +829,11 @@
      * For security and performance reasons this method does not support
      * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION} or
      * {@link Intent#FLAG_GRANT_PREFIX_URI_PERMISSION}.
+     * <p>
+     * The write access granted through this request is general-purpose, and
+     * once obtained you can directly {@link ContentResolver#update} columns
+     * like {@link MediaColumns#IS_FAVORITE}, {@link MediaColumns#IS_TRASHED},
+     * or {@link ContentResolver#delete}.
      *
      * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
      *            Typically this value is {@link Context#getContentResolver()},
@@ -805,7 +858,8 @@
      * call {@link Activity#startIntentSenderForResult} with
      * {@link PendingIntent#getIntentSender()}. You can then determine if the
      * user granted your request by testing for {@link Activity#RESULT_OK} in
-     * {@link Activity#onActivityResult}.
+     * {@link Activity#onActivityResult}. The requested operation will have
+     * completely finished before this activity result is delivered.
      * <p>
      * The displayed prompt will reflect all the media items you're requesting,
      * including those for which you already hold write access. If you want to
@@ -848,7 +902,8 @@
      * call {@link Activity#startIntentSenderForResult} with
      * {@link PendingIntent#getIntentSender()}. You can then determine if the
      * user granted your request by testing for {@link Activity#RESULT_OK} in
-     * {@link Activity#onActivityResult}.
+     * {@link Activity#onActivityResult}. The requested operation will have
+     * completely finished before this activity result is delivered.
      * <p>
      * The displayed prompt will reflect all the media items you're requesting,
      * including those for which you already hold write access. If you want to
@@ -888,7 +943,8 @@
      * call {@link Activity#startIntentSenderForResult} with
      * {@link PendingIntent#getIntentSender()}. You can then determine if the
      * user granted your request by testing for {@link Activity#RESULT_OK} in
-     * {@link Activity#onActivityResult}.
+     * {@link Activity#onActivityResult}. The requested operation will have
+     * completely finished before this activity result is delivered.
      * <p>
      * The displayed prompt will reflect all the media items you're requesting,
      * including those for which you already hold write access. If you want to
@@ -1224,6 +1280,13 @@
          * than using {@link #DATE_ADDED}, since those values may change in
          * unexpected ways when apps use {@link File#setLastModified(long)} or
          * when the system clock is set incorrectly.
+         * <p>
+         * Note that before comparing these detailed generation values, you
+         * should first confirm that the overall version hasn't changed by
+         * checking {@link MediaStore#getVersion(Context, String)}, since that
+         * indicates when a more radical change has occurred. If the overall
+         * version changes, you should assume that generation numbers have been
+         * reset and perform a full synchronization pass.
          *
          * @see MediaStore#getGeneration(Context, String)
          */
@@ -1241,6 +1304,13 @@
          * using {@link #DATE_MODIFIED}, since those values may change in
          * unexpected ways when apps use {@link File#setLastModified(long)} or
          * when the system clock is set incorrectly.
+         * <p>
+         * Note that before comparing these detailed generation values, you
+         * should first confirm that the overall version hasn't changed by
+         * checking {@link MediaStore#getVersion(Context, String)}, since that
+         * indicates when a more radical change has occurred. If the overall
+         * version changes, you should assume that generation numbers have been
+         * reset and perform a full synchronization pass.
          *
          * @see MediaStore#getGeneration(Context, String)
          */
@@ -2255,7 +2325,13 @@
             /**
              * Return the typical {@link Size} (in pixels) used internally when
              * the given thumbnail kind is requested.
+             *
+             * @deprecated Callers should migrate to using
+             *             {@link ContentResolver#loadThumbnail}, since it
+             *             offers richer control over requested thumbnail sizes
+             *             and cancellation behavior.
              */
+            @Deprecated
             public static @NonNull Size getKindSize(int kind) {
                 return ThumbnailConstants.getKindSize(kind);
             }
@@ -3525,7 +3601,13 @@
             /**
              * Return the typical {@link Size} (in pixels) used internally when
              * the given thumbnail kind is requested.
+             *
+             * @deprecated Callers should migrate to using
+             *             {@link ContentResolver#loadThumbnail}, since it
+             *             offers richer control over requested thumbnail sizes
+             *             and cancellation behavior.
              */
+            @Deprecated
             public static @NonNull Size getKindSize(int kind) {
                 return ThumbnailConstants.getKindSize(kind);
             }
@@ -3720,6 +3802,13 @@
      * {@link MediaColumns#DATE_MODIFIED}, since those values may change in
      * unexpected ways when apps use {@link File#setLastModified(long)} or when
      * the system clock is set incorrectly.
+     * <p>
+     * Note that before comparing these detailed generation values, you should
+     * first confirm that the overall version hasn't changed by checking
+     * {@link MediaStore#getVersion(Context, String)}, since that indicates when
+     * a more radical change has occurred. If the overall version changes, you
+     * should assume that generation numbers have been reset and perform a full
+     * synchronization pass.
      *
      * @param volumeName specific volume to obtain an generation value for. Must
      *            be one of the values returned from
@@ -3808,6 +3897,7 @@
      */
     @SystemApi
     @TestApi
+    @WorkerThread
     public static void waitForIdle(@NonNull ContentResolver resolver) {
         resolver.call(AUTHORITY, WAIT_FOR_IDLE_CALL, null, null);
     }
@@ -3820,6 +3910,7 @@
      */
     @SystemApi
     @TestApi
+    @WorkerThread
     @SuppressLint("StreamFiles")
     public static @NonNull Uri scanFile(@NonNull ContentResolver resolver, @NonNull File file) {
         final Bundle out = resolver.call(AUTHORITY, SCAN_FILE_CALL, file.getAbsolutePath(), null);
@@ -3833,6 +3924,7 @@
      */
     @SystemApi
     @TestApi
+    @WorkerThread
     public static void scanVolume(@NonNull ContentResolver resolver, @NonNull String volumeName) {
         resolver.call(AUTHORITY, SCAN_VOLUME_CALL, volumeName, null);
     }
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index 42fdd05..95529a3 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -841,25 +841,30 @@
         return;
     }
 
-    if (ri->isRedactionNeeded() || is_file_locked(fd, path)) {
-        // We don't want to use the FUSE VFS cache in two cases:
-        // 1. When redaction is needed because app A with EXIF access might access
-        // a region that should have been redacted for app B without EXIF access, but app B on
-        // a subsequent read, will be able to see the EXIF data because the read request for that
-        // region will be served from cache and not get to the FUSE daemon
-        // 2. When the file has a read or write lock on it. This means that the MediaProvider has
-        // given an fd to the lower file system to an app. There are two cases where using the cache
-        // in this case can be a problem:
-        // a. Writing to a FUSE fd with caching enabled will use the write-back cache and a
-        // subsequent read from the lower fs fd will not see the write.
-        // b. Reading from a FUSE fd with caching enabled may not see the latest writes using the
-        // lower fs fd because those writes did not go through the FUSE layer and reads from FUSE
-        // after that write may be served from cache
-        fi->direct_io = true;
-    }
+    handle* h = nullptr;
+    {
+        std::lock_guard<std::recursive_mutex> guard(fuse->lock);
 
-    handle* h = new handle(path, fd, ri.release(), /*owner_uid*/ -1, !fi->direct_io);
-    node->AddHandle(h);
+        if (ri->isRedactionNeeded() || is_file_locked(fd, path)) {
+            // We don't want to use the FUSE VFS cache in two cases:
+            // 1. When redaction is needed because app A with EXIF access might access
+            // a region that should have been redacted for app B without EXIF access, but app B on
+            // a subsequent read, will be able to see the EXIF data because the read request for
+            // that region will be served from cache and not get to the FUSE daemon
+            // 2. When the file has a read or write lock on it. This means that the MediaProvider
+            // has given an fd to the lower file system to an app. There are two cases where using
+            // the cache in this case can be a problem:
+            // a. Writing to a FUSE fd with caching enabled will use the write-back cache and a
+            // subsequent read from the lower fs fd will not see the write.
+            // b. Reading from a FUSE fd with caching enabled may not see the latest writes using
+            // the lower fs fd because those writes did not go through the FUSE layer and reads from
+            // FUSE after that write may be served from cache
+            fi->direct_io = true;
+        }
+
+        h = new handle(path, fd, ri.release(), /*owner_uid*/ -1, !fi->direct_io);
+        node->AddHandle(h);
+    }
 
     fi->fh = ptr_to_id(h);
     fi->keep_cache = 1;
@@ -1468,6 +1473,7 @@
     bool use_fuse = false;
 
     if (active.load(std::memory_order_acquire)) {
+        std::lock_guard<std::recursive_mutex> guard(fuse->lock);
         const node* node = node::LookupAbsolutePath(fuse->root, path);
         if (node && node->HasCachedHandle()) {
             use_fuse = true;
@@ -1550,8 +1556,6 @@
         LOG(FATAL) << "mmap failed - could not start fuse! errno = " << errno;
     }
 
-    umask(0);
-
     // Custom logging for libfuse
     fuse_set_log_func(fuse_logger);
 
diff --git a/legacy/src/com/android/providers/media/LegacyMediaProvider.java b/legacy/src/com/android/providers/media/LegacyMediaProvider.java
index 93d1b8e..b79a955 100644
--- a/legacy/src/com/android/providers/media/LegacyMediaProvider.java
+++ b/legacy/src/com/android/providers/media/LegacyMediaProvider.java
@@ -26,6 +26,7 @@
 import android.content.pm.ProviderInfo;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Bundle;
 import android.provider.MediaStore;
 import android.provider.MediaStore.MediaColumns;
 
@@ -37,6 +38,7 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.Objects;
 
 /**
  * Very limited subset of {@link MediaProvider} which only surfaces
@@ -46,6 +48,9 @@
     private DatabaseHelper mInternalDatabase;
     private DatabaseHelper mExternalDatabase;
 
+    public static final String START_LEGACY_MIGRATION_CALL = "start_legacy_migration";
+    public static final String FINISH_LEGACY_MIGRATION_CALL = "finish_legacy_migration";
+
     @Override
     public void attachInfo(Context context, ProviderInfo info) {
         // Sanity check our setup
@@ -68,9 +73,9 @@
         Logging.initPersistent(persistentDir);
 
         mInternalDatabase = new DatabaseHelper(context, INTERNAL_DATABASE_NAME,
-                true, false, true, null, null, null);
+                true, false, true, null, null, null, null);
         mExternalDatabase = new DatabaseHelper(context, EXTERNAL_DATABASE_NAME,
-                false, false, true, null, null, null);
+                false, false, true, null, null, null, null);
 
         return true;
     }
@@ -79,9 +84,9 @@
         final String volumeName = MediaStore.getVolumeName(uri);
         switch (volumeName) {
             case MediaStore.VOLUME_INTERNAL:
-                return mInternalDatabase;
+                return Objects.requireNonNull(mInternalDatabase, "Missing internal database");
             default:
-                return mExternalDatabase;
+                return Objects.requireNonNull(mExternalDatabase, "Missing external database");
         }
     }
 
@@ -124,6 +129,37 @@
     }
 
     @Override
+    public Bundle call(String authority, String method, String arg, Bundle extras) {
+        switch (method) {
+            case START_LEGACY_MIGRATION_CALL: {
+                // Nice to know, but nothing actionable
+                break;
+            }
+            case FINISH_LEGACY_MIGRATION_CALL: {
+                // We're only going to hear this once, since we've either
+                // successfully migrated legacy data, or we're never going to
+                // try again, so it's time to clean things up
+                final String volumeName = arg;
+                switch (volumeName) {
+                    case MediaStore.VOLUME_INTERNAL: {
+                        mInternalDatabase.close();
+                        mInternalDatabase = null;
+                        getContext().deleteDatabase(INTERNAL_DATABASE_NAME);
+                        break;
+                    }
+                    default: {
+                        mExternalDatabase.close();
+                        mExternalDatabase = null;
+                        getContext().deleteDatabase(EXTERNAL_DATABASE_NAME);
+                        break;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         Logging.dumpPersistent(writer);
     }
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 85e2394..612e9cd 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -34,14 +34,10 @@
       <item quantity="other">அத்துடன் கூடுதலாக <xliff:g id="COUNT_1">^1</xliff:g></item>
       <item quantity="one">அத்துடன் கூடுதலாக <xliff:g id="COUNT_0">^1</xliff:g></item>
     </plurals>
-    <!-- no translation found for cache_clearing_dialog_title (543177167845854283) -->
-    <skip />
-    <!-- no translation found for cache_clearing_dialog_text (425995541409682360) -->
-    <skip />
-    <!-- no translation found for allow (8885707816848569619) -->
-    <skip />
-    <!-- no translation found for deny (6040983710442068936) -->
-    <skip />
+    <string name="cache_clearing_dialog_title" msgid="543177167845854283">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> மூலம் தற்காலிகச் சேமிப்பை அகற்ற விரும்புகிறீர்களா?"</string>
+    <string name="cache_clearing_dialog_text" msgid="425995541409682360">"சில தற்காலிகக் கோப்புகளை நீக்குவதற்கு <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> அனுமதி கேட்கின்றது. ஏற்றுக்கொண்டால் பேட்டரி அல்லது டேட்டா உபயோகம் அதிகரிக்கக்கூடும்."</string>
+    <string name="allow" msgid="8885707816848569619">"அனுமதி"</string>
+    <string name="deny" msgid="6040983710442068936">"நிராகரி"</string>
     <plurals name="permission_write_audio" formatted="false" msgid="3539998638571517689">
       <item quantity="other"><xliff:g id="COUNT">^2</xliff:g> ஆடியோ ஃபைல்களை மாற்ற <xliff:g id="APP_NAME_1">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
       <item quantity="one">இந்த ஆடியோ ஃபைலை மாற்ற <xliff:g id="APP_NAME_0">^1</xliff:g> ஆப்ஸை அனுமதிக்கவா?</item>
diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java
index 29360c5..05276b3 100644
--- a/src/com/android/providers/media/DatabaseHelper.java
+++ b/src/com/android/providers/media/DatabaseHelper.java
@@ -51,15 +51,16 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseArray;
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.providers.media.util.BackgroundThread;
 import com.android.providers.media.util.DatabaseUtils;
 import com.android.providers.media.util.FileUtils;
+import com.android.providers.media.util.ForegroundThread;
 import com.android.providers.media.util.Logging;
 import com.android.providers.media.util.MimeUtils;
 
@@ -67,8 +68,6 @@
 import java.io.FilenameFilter;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
@@ -101,6 +100,7 @@
     final @Nullable Class<? extends Annotation> mColumnAnnotation;
     final @Nullable OnSchemaChangeListener mSchemaListener;
     final @Nullable OnFilesChangeListener mFilesListener;
+    final @Nullable OnLegacyMigrationListener mMigrationListener;
     final Set<String> mFilterVolumeNames = new ArraySet<>();
     long mScanStartTime;
     long mScanStopTime;
@@ -122,20 +122,27 @@
                 int mediaType, boolean isDownload);
     }
 
+    public interface OnLegacyMigrationListener {
+        public void onStarted(ContentProviderClient client, String volumeName);
+        public void onFinished(ContentProviderClient client, String volumeName);
+    }
+
     public DatabaseHelper(Context context, String name,
             boolean internal, boolean earlyUpgrade, boolean legacyProvider,
             @Nullable Class<? extends Annotation> columnAnnotation,
             @Nullable OnSchemaChangeListener schemaListener,
-            @Nullable OnFilesChangeListener filesListener) {
+            @Nullable OnFilesChangeListener filesListener,
+            @Nullable OnLegacyMigrationListener migrationListener) {
         this(context, name, getDatabaseVersion(context), internal, earlyUpgrade, legacyProvider,
-                columnAnnotation, schemaListener, filesListener);
+                columnAnnotation, schemaListener, filesListener, migrationListener);
     }
 
     public DatabaseHelper(Context context, String name, int version,
             boolean internal, boolean earlyUpgrade, boolean legacyProvider,
             @Nullable Class<? extends Annotation> columnAnnotation,
             @Nullable OnSchemaChangeListener schemaListener,
-            @Nullable OnFilesChangeListener filesListener) {
+            @Nullable OnFilesChangeListener filesListener,
+            @Nullable OnLegacyMigrationListener migrationListener) {
         super(context, name, null, version);
         mContext = context;
         mName = name;
@@ -147,6 +154,7 @@
         mColumnAnnotation = columnAnnotation;
         mSchemaListener = schemaListener;
         mFilesListener = filesListener;
+        mMigrationListener = migrationListener;
 
         // Configure default filters until we hear differently
         if (mInternal) {
@@ -331,11 +339,11 @@
         public boolean successful;
 
         /**
-         * List of {@link Uri} that would have been sent directly via
-         * {@link ContentResolver#notifyChange}, but are instead being collected
-         * due to this ongoing transaction.
+         * Map from {@code flags} value to set of {@link Uri} that would have
+         * been sent directly via {@link ContentResolver#notifyChange}, but are
+         * instead being collected due to this ongoing transaction.
          */
-        public final Set<Uri> notifyChanges = new ArraySet<>();
+        public final SparseArray<ArraySet<Uri>> notifyChanges = new SparseArray<>();
     }
 
     public void beginTransaction() {
@@ -371,8 +379,11 @@
         db.endTransaction();
 
         if (state.successful) {
-            BackgroundThread.getExecutor().execute(() -> {
-                notifyChangeInternal(state.notifyChanges);
+            ForegroundThread.getExecutor().execute(() -> {
+                for (int i = 0; i < state.notifyChanges.size(); i++) {
+                    notifyChangeInternal(state.notifyChanges.valueAt(i),
+                            state.notifyChanges.keyAt(i));
+                }
             });
         }
     }
@@ -399,36 +410,53 @@
         }
     }
 
+    public void notifyInsert(@NonNull Uri uri) {
+        notifyChange(uri, ContentResolver.NOTIFY_INSERT);
+    }
+
+    public void notifyUpdate(@NonNull Uri uri) {
+        notifyChange(uri, ContentResolver.NOTIFY_UPDATE);
+    }
+
+    public void notifyDelete(@NonNull Uri uri) {
+        notifyChange(uri, ContentResolver.NOTIFY_DELETE);
+    }
+
     /**
      * Notify that the given {@link Uri} has changed. This enqueues the
      * notification if currently inside a transaction, and they'll be
      * clustered and sent when the transaction completes.
      */
-    public void notifyChange(@NonNull Uri uri) {
+    public void notifyChange(@NonNull Uri uri, int flags) {
         if (LOGV) Log.v(TAG, "Notifying " + uri);
         final TransactionState state = mTransactionState.get();
         if (state != null) {
-            state.notifyChanges.add(uri);
+            ArraySet<Uri> set = state.notifyChanges.get(flags);
+            if (set == null) {
+                set = new ArraySet<>();
+                state.notifyChanges.put(flags, set);
+            }
+            set.add(uri);
         } else {
-            BackgroundThread.getExecutor().execute(() -> {
-                notifySingleChangeInternal(uri);
+            ForegroundThread.getExecutor().execute(() -> {
+                notifySingleChangeInternal(uri, flags);
             });
         }
     }
 
-    private void notifySingleChangeInternal(Uri uri) {
+    private void notifySingleChangeInternal(@NonNull Uri uri, int flags) {
         Trace.beginSection("notifySingleChange");
         try {
-            mContext.getContentResolver().notifyChange(uri, null, 0);
+            mContext.getContentResolver().notifyChange(uri, null, flags);
         } finally {
             Trace.endSection();
         }
     }
 
-    private void notifyChangeInternal(Iterable<Uri> uris) {
+    private void notifyChangeInternal(@NonNull Iterable<Uri> uris, int flags) {
         Trace.beginSection("notifyChange");
         try {
-            mContext.getContentResolver().notifyChange(uris, null, 0);
+            mContext.getContentResolver().notifyChange(uris, null, flags);
         } finally {
             Trace.endSection();
         }
@@ -617,6 +645,7 @@
 
             db.execSQL("SAVEPOINT before_migrate");
             Log.d(TAG, "Starting migration from legacy provider for " + mName);
+            mMigrationListener.onStarted(client, mVolumeName);
             try (Cursor c = client.query(queryUri, sMigrateColumns.toArray(new String[0]),
                     extras, null)) {
                 final ContentValues values = new ContentValues();
@@ -635,11 +664,13 @@
 
                 db.execSQL("RELEASE before_migrate");
                 Log.d(TAG, "Finished migration from legacy provider for " + mName);
+                mMigrationListener.onFinished(client, mVolumeName);
             } catch (Exception e) {
                 // We have to guard ourselves against any weird behavior of the
                 // legacy provider by trying to catch everything
                 db.execSQL("ROLLBACK TO before_migrate");
                 Log.w(TAG, "Failed migration from legacy provider: " + e);
+                mMigrationListener.onFinished(client, mVolumeName);
             }
         }
     }
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 172aa41..d2af07d 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -162,6 +162,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.providers.media.DatabaseHelper.OnFilesChangeListener;
+import com.android.providers.media.DatabaseHelper.OnLegacyMigrationListener;
 import com.android.providers.media.fuse.ExternalStorageServiceImpl;
 import com.android.providers.media.fuse.FuseDaemon;
 import com.android.providers.media.scan.MediaScanner;
@@ -171,6 +172,7 @@
 import com.android.providers.media.util.CachedSupplier;
 import com.android.providers.media.util.DatabaseUtils;
 import com.android.providers.media.util.FileUtils;
+import com.android.providers.media.util.ForegroundThread;
 import com.android.providers.media.util.IsoInterface;
 import com.android.providers.media.util.Logging;
 import com.android.providers.media.util.LongArray;
@@ -198,7 +200,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
@@ -475,7 +476,7 @@
         @Override
         public void onInsert(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
                 int mediaType, boolean isDownload) {
-            acceptWithExpansion(helper::notifyChange, volumeName, id, mediaType, isDownload);
+            acceptWithExpansion(helper::notifyInsert, volumeName, id, mediaType, isDownload);
 
             if (helper.isExternal()) {
                 // Update the quota type on the filesystem
@@ -492,7 +493,7 @@
                 int oldMediaType, boolean oldIsDownload,
                 int newMediaType, boolean newIsDownload) {
             final boolean isDownload = oldIsDownload || newIsDownload;
-            acceptWithExpansion(helper::notifyChange, volumeName, id, oldMediaType, isDownload);
+            acceptWithExpansion(helper::notifyUpdate, volumeName, id, oldMediaType, isDownload);
 
             // When media type changes, notify both old and new collections and
             // invalidate any thumbnails
@@ -501,7 +502,7 @@
                 if (helper.isExternal()) {
                     updateQuotaTypeForUri(fileUri, newMediaType);
                 }
-                acceptWithExpansion(helper::notifyChange, volumeName, id, newMediaType, isDownload);
+                acceptWithExpansion(helper::notifyUpdate, volumeName, id, newMediaType, isDownload);
                 invalidateThumbnails(fileUri);
             }
         }
@@ -512,7 +513,7 @@
             // Both notify apps and revoke any outstanding permission grants
             final Context context = getContext();
             acceptWithExpansion((uri) -> {
-                helper.notifyChange(uri);
+                helper.notifyDelete(uri);
                 context.revokeUriPermission(uri, ~0);
             }, volumeName, id, mediaType, isDownload);
 
@@ -524,6 +525,18 @@
         }
     };
 
+    private final OnLegacyMigrationListener mMigrationListener = new OnLegacyMigrationListener() {
+        @Override
+        public void onStarted(ContentProviderClient client, String volumeName) {
+            MediaStore.startLegacyMigration(ContentResolver.wrap(client), volumeName);
+        }
+
+        @Override
+        public void onFinished(ContentProviderClient client, String volumeName) {
+            MediaStore.finishLegacyMigration(ContentResolver.wrap(client), volumeName);
+        }
+    };
+
     /**
      * Apply {@link Consumer#accept} to the given item.
      * <p>
@@ -583,6 +596,8 @@
             Environment.DIRECTORY_MOVIES,
             Environment.DIRECTORY_DOWNLOADS,
             Environment.DIRECTORY_DCIM,
+            Environment.DIRECTORY_AUDIOBOOKS,
+            Environment.DIRECTORY_DOCUMENTS,
     };
 
     private static boolean isDefaultDirectoryName(@Nullable String dirName) {
@@ -671,10 +686,10 @@
 
         mInternalDatabase = new DatabaseHelper(context, INTERNAL_DATABASE_NAME,
                 true, false, mLegacyProvider, Column.class,
-                Metrics::logSchemaChange, mFilesListener);
+                Metrics::logSchemaChange, mFilesListener, mMigrationListener);
         mExternalDatabase = new DatabaseHelper(context, EXTERNAL_DATABASE_NAME,
                 false, false, mLegacyProvider, Column.class,
-                Metrics::logSchemaChange, mFilesListener);
+                Metrics::logSchemaChange, mFilesListener, mMigrationListener);
 
         final IntentFilter filter = new IntentFilter();
         filter.setPriority(10);
@@ -1909,6 +1924,7 @@
                 defaultPrimary = Environment.DIRECTORY_MUSIC;
                 allowedPrimary = Arrays.asList(
                         Environment.DIRECTORY_ALARMS,
+                        Environment.DIRECTORY_AUDIOBOOKS,
                         Environment.DIRECTORY_MUSIC,
                         Environment.DIRECTORY_NOTIFICATIONS,
                         Environment.DIRECTORY_PODCASTS,
@@ -3860,15 +3876,8 @@
     private Bundle callInternal(String method, String arg, Bundle extras) {
         switch (method) {
             case MediaStore.WAIT_FOR_IDLE_CALL: {
-                final CountDownLatch latch = new CountDownLatch(1);
-                BackgroundThread.getExecutor().execute(() -> {
-                    latch.countDown();
-                });
-                try {
-                    latch.await(30, TimeUnit.SECONDS);
-                } catch (InterruptedException e) {
-                    throw new IllegalStateException(e);
-                }
+                ForegroundThread.waitForIdle();
+                BackgroundThread.waitForIdle();
                 return null;
             }
             case MediaStore.SCAN_FILE_CALL:
diff --git a/src/com/android/providers/media/MediaUpgradeReceiver.java b/src/com/android/providers/media/MediaUpgradeReceiver.java
index ee3cb68..dd99fc4 100644
--- a/src/com/android/providers/media/MediaUpgradeReceiver.java
+++ b/src/com/android/providers/media/MediaUpgradeReceiver.java
@@ -69,7 +69,7 @@
                     try {
                         DatabaseHelper helper = new DatabaseHelper(
                                 context, file, MediaProvider.isInternalMediaDatabaseName(file),
-                                false, false, Column.class, Metrics::logSchemaChange, null);
+                                false, false, Column.class, Metrics::logSchemaChange, null, null);
                         db = helper.getWritableDatabase();
                     } catch (Throwable t) {
                         Log.wtf(TAG, "Error during upgrade of media db " + file, t);
diff --git a/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java b/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java
index 4ce2d2b..3fd1b4f 100644
--- a/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java
+++ b/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java
@@ -28,6 +28,7 @@
 
 import com.android.providers.media.MediaProvider;
 
+import java.io.File;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -42,8 +43,8 @@
 
     @Override
     public void onStartSession(String sessionId, /* @SessionFlag */ int flag,
-            @NonNull ParcelFileDescriptor deviceFd, @NonNull String upperFileSystemPath,
-            @NonNull String lowerFileSystemPath) {
+            @NonNull ParcelFileDescriptor deviceFd, @NonNull File upperFileSystemPath,
+            @NonNull File lowerFileSystemPath) {
         MediaProvider mediaProvider = getMediaProvider();
 
         synchronized (sLock) {
@@ -55,7 +56,7 @@
                 // REMOUNT_MODE_PASS_THROUGH which guarantees that all /storage paths are bind
                 // mounts of the lower filesystem.
                 FuseDaemon daemon = new FuseDaemon(mediaProvider, this, deviceFd, sessionId,
-                        upperFileSystemPath);
+                        upperFileSystemPath.getPath());
                 daemon.start();
                 sFuseDaemons.put(sessionId, daemon);
             }
diff --git a/src/com/android/providers/media/util/BackgroundThread.java b/src/com/android/providers/media/util/BackgroundThread.java
index 7b9cb54..dc03112 100644
--- a/src/com/android/providers/media/util/BackgroundThread.java
+++ b/src/com/android/providers/media/util/BackgroundThread.java
@@ -19,7 +19,9 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 public final class BackgroundThread extends HandlerThread {
     private static BackgroundThread sInstance;
@@ -27,7 +29,7 @@
     private static HandlerExecutor sHandlerExecutor;
 
     private BackgroundThread() {
-        super("android.bg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+        super("bg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
     }
 
     private static void ensureThreadLocked() {
@@ -59,4 +61,16 @@
             return sHandlerExecutor;
         }
     }
+
+    public static void waitForIdle() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        getExecutor().execute(() -> {
+            latch.countDown();
+        });
+        try {
+            latch.await(30, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new IllegalStateException(e);
+        }
+    }
 }
diff --git a/src/com/android/providers/media/util/ForegroundThread.java b/src/com/android/providers/media/util/ForegroundThread.java
new file mode 100644
index 0000000..67388fc
--- /dev/null
+++ b/src/com/android/providers/media/util/ForegroundThread.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 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.util;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+public final class ForegroundThread extends HandlerThread {
+    private static ForegroundThread sInstance;
+    private static Handler sHandler;
+    private static HandlerExecutor sHandlerExecutor;
+
+    private ForegroundThread() {
+        super("fg", android.os.Process.THREAD_PRIORITY_FOREGROUND);
+    }
+
+    private static void ensureThreadLocked() {
+        if (sInstance == null) {
+            sInstance = new ForegroundThread();
+            sInstance.start();
+            sHandler = new Handler(sInstance.getLooper());
+            sHandlerExecutor = new HandlerExecutor(sHandler);
+        }
+    }
+
+    public static ForegroundThread get() {
+        synchronized (ForegroundThread.class) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+
+    public static Handler getHandler() {
+        synchronized (ForegroundThread.class) {
+            ensureThreadLocked();
+            return sHandler;
+        }
+    }
+
+    public static Executor getExecutor() {
+        synchronized (ForegroundThread.class) {
+            ensureThreadLocked();
+            return sHandlerExecutor;
+        }
+    }
+
+    public static void waitForIdle() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        getExecutor().execute(() -> {
+            latch.countDown();
+        });
+        try {
+            latch.await(30, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}
diff --git a/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java b/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java
index 04e2b5c..ae63ac3 100644
--- a/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java
+++ b/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java
@@ -188,6 +188,9 @@
         Assume.assumeNotNull(legacyProvider);
         Assume.assumeNotNull(modernProvider);
 
+        // Clear data on the legacy provider so that we create a database
+        executeShellCommand("pm clear " + legacyProvider.applicationInfo.packageName, ui);
+
         // Create a well-known entry in legacy provider, and write data into
         // place to ensure the file is created on disk
         final Uri legacyUri;
diff --git a/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java b/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java
index 2825040..10b063a 100644
--- a/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java
+++ b/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/FuseDaemonHostTest.java
@@ -49,14 +49,13 @@
 
     @Before
     public void setup() throws Exception {
-        executeShellCommand("mkdir /sdcard/Android/data/com.android.shell");
-        executeShellCommand("mkdir /sdcard/Android/data/com.android.shell/files");
+        executeShellCommand("mkdir /sdcard/Android/data/com.android.shell -m 2770");
+        executeShellCommand("mkdir /sdcard/Android/data/com.android.shell/files -m 2770");
     }
 
     @After
     public void tearDown() throws Exception {
         executeShellCommand("rm -r /sdcard/Android/data/com.android.shell");
-        executeShellCommand("rm -r /sdcard/Android/data/com.android.shell/files");
     }
 
     @Test
diff --git a/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/LegacyAccessHostTest.java b/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/LegacyAccessHostTest.java
index 852ab0e..9ebdfe8 100644
--- a/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/LegacyAccessHostTest.java
+++ b/tests/jni/FuseDaemonTest/host/src/com/android/tests/fused/host/LegacyAccessHostTest.java
@@ -195,4 +195,20 @@
     public void testLegacyAppCanOwnAFile_hasW() throws Exception {
         runDeviceTest("testLegacyAppCanOwnAFile_hasW");
     }
+
+    @Test
+    public void testCreateAndRenameDoesntLeaveStaleDBRow_hasRW() throws Exception {
+        runDeviceTest("testCreateAndRenameDoesntLeaveStaleDBRow_hasRW");
+    }
+
+    @Test
+    public void testRenameDoesntInvalidateUri_hasRW() throws Exception {
+        runDeviceTest("testRenameDoesntInvalidateUri_hasRW");
+    }
+
+
+    @Test
+    public void testCanRenameAFileWithNoDBRow_hasRW() throws Exception {
+        runDeviceTest("testCanRenameAFileWithNoDBRow_hasRW");
+    }
 }
diff --git a/tests/jni/FuseDaemonTest/legacy/src/com/android/tests/fused/legacy/LegacyFileAccessTest.java b/tests/jni/FuseDaemonTest/legacy/src/com/android/tests/fused/legacy/LegacyFileAccessTest.java
index 8dfbb4d..b28f7fc 100644
--- a/tests/jni/FuseDaemonTest/legacy/src/com/android/tests/fused/legacy/LegacyFileAccessTest.java
+++ b/tests/jni/FuseDaemonTest/legacy/src/com/android/tests/fused/legacy/LegacyFileAccessTest.java
@@ -16,13 +16,17 @@
 
 package com.android.tests.fused.legacy;
 
+import static com.android.tests.fused.lib.TestUtils.BYTES_DATA1;
+import static com.android.tests.fused.lib.TestUtils.BYTES_DATA2;
+import static com.android.tests.fused.lib.TestUtils.STR_DATA1;
+import static com.android.tests.fused.lib.TestUtils.STR_DATA2;
 import static com.android.tests.fused.lib.TestUtils.assertCanRenameFile;
 import static com.android.tests.fused.lib.TestUtils.assertCanRenameDirectory;
 import static com.android.tests.fused.lib.TestUtils.assertCantRenameFile;
+import static com.android.tests.fused.lib.TestUtils.assertFileContent;
 import static com.android.tests.fused.lib.TestUtils.createFileAs;
-
-
 import static com.android.tests.fused.lib.TestUtils.deleteFileAsNoThrow;
+import static com.android.tests.fused.lib.TestUtils.getContentResolver;
 import static com.android.tests.fused.lib.TestUtils.getFileOwnerPackageFromDatabase;
 import static com.android.tests.fused.lib.TestUtils.getFileRowIdFromDatabase;
 import static com.android.tests.fused.lib.TestUtils.installApp;
@@ -36,11 +40,17 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.Manifest;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
 import android.os.Environment;
+import android.provider.MediaStore;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
@@ -52,6 +62,8 @@
 import com.android.cts.install.lib.TestApp;
 import com.android.tests.fused.lib.ReaddirTestHelper;
 
+import com.google.common.io.Files;
+
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -59,7 +71,9 @@
 
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 
 /**
@@ -76,6 +90,7 @@
     private static final String TAG = "LegacyFileAccessTest";
     static final String THIS_PACKAGE_NAME = InstrumentationRegistry.getContext().getPackageName();
 
+    static final String IMAGE_FILE_NAME = "FilePathAccessTest_file.jpg";
     static final String VIDEO_FILE_NAME = "LegacyAccessTest_file.mp4";
     static final String NONMEDIA_FILE_NAME = "LegacyAccessTest_file.pdf";
 
@@ -460,6 +475,134 @@
         }
     }
 
+    /**
+     * b/14966134: Test that FuseDaemon doesn't leave stale database entries after create() and
+     * rename().
+     */
+    @Test
+    public void testCreateAndRenameDoesntLeaveStaleDBRow_hasRW() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+        final File directoryDCIM = new File(Environment.getExternalStorageDirectory(),
+                Environment.DIRECTORY_DCIM);
+        final File videoFile = new File(directoryDCIM, VIDEO_FILE_NAME);
+        final File renamedVideoFile = new File(directoryDCIM, "Renamed_" + VIDEO_FILE_NAME);
+        final ContentResolver cr = getContentResolver();
+
+        try {
+            assertThat(videoFile.createNewFile()).isTrue();
+            assertThat(videoFile.renameTo(renamedVideoFile)).isTrue();
+
+            ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.DATA, renamedVideoFile.getAbsolutePath());
+            // Insert new renamedVideoFile to database
+            final Uri uri = cr.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values, null);
+            assertNotNull(uri);
+
+            // Query for all images/videos in the device.
+            // This shouldn't list videoFile which was renamed to renamedVideoFile.
+            final ArrayList<String> imageAndVideoFiles = getImageAndVideoFilesFromDatabase();
+            assertThat(imageAndVideoFiles).contains(renamedVideoFile.getName());
+            assertThat(imageAndVideoFiles).doesNotContain(videoFile.getName());
+        } finally {
+            videoFile.delete();
+            renamedVideoFile.delete();
+            MediaStore.scanFile(cr, renamedVideoFile);
+        }
+    }
+
+    /**
+     * b/150147690,b/150193381: Test that file rename doesn't delete any existing Uri.
+     */
+    @Test
+    public void testRenameDoesntInvalidateUri_hasRW() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+        final File directoryDCIM = new File(Environment.getExternalStorageDirectory(),
+                Environment.DIRECTORY_DCIM);
+        final File imageFile = new File(directoryDCIM, IMAGE_FILE_NAME);
+        final File temporaryImageFile = new File(directoryDCIM, IMAGE_FILE_NAME + "_.tmp");
+        final ContentResolver cr = getContentResolver();
+
+        try {
+            assertThat(imageFile.createNewFile()).isTrue();
+            try (final FileOutputStream fos = new FileOutputStream(imageFile)) {
+                fos.write(BYTES_DATA1);
+            }
+            // Insert this file to database.
+            ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.DATA, imageFile.getAbsolutePath());
+            final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values, null);
+            assertNotNull(uri);
+
+            Files.copy(imageFile, temporaryImageFile);
+            // Write more bytes to temporaryImageFile
+            try (final FileOutputStream fos = new FileOutputStream(temporaryImageFile, true)) {
+                fos.write(BYTES_DATA2);
+            }
+            assertThat(imageFile.delete()).isTrue();
+            temporaryImageFile.renameTo(imageFile);
+
+            // Previous uri of imageFile is unaltered after delete & rename.
+            final Uri scannedUri = MediaStore.scanFile(cr, imageFile);
+            assertThat(scannedUri.getLastPathSegment()).isEqualTo(uri.getLastPathSegment());
+
+            final byte[] expected = (STR_DATA1 + STR_DATA2).getBytes();
+            assertFileContent(imageFile, expected);
+        } finally {
+            imageFile.delete();
+            temporaryImageFile.delete();
+            MediaStore.scanFile(cr, imageFile);
+        }
+    }
+
+    /**
+     * b/150498564,b/150274099: Test that apps can rename files that are not in database.
+     */
+    @Test
+    public void testCanRenameAFileWithNoDBRow_hasRW() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+        final File directoryDCIM = new File(Environment.getExternalStorageDirectory(),
+                Environment.DIRECTORY_DCIM);
+        final File directoryNoMedia = new File(directoryDCIM, ".directoryNoMedia");
+        final File imageInNoMediaDir = new File(directoryNoMedia, IMAGE_FILE_NAME);
+        final File renamedImageInDCIM = new File(directoryDCIM, IMAGE_FILE_NAME);
+        final File noMediaFile = new File(directoryNoMedia, ".nomedia");
+        final ContentResolver cr = getContentResolver();
+
+        try {
+            if (!directoryNoMedia.exists()) {
+                assertThat(directoryNoMedia.mkdirs()).isTrue();
+            }
+            assertThat(noMediaFile.createNewFile()).isTrue();
+            assertThat(imageInNoMediaDir.createNewFile()).isTrue();
+            // Remove imageInNoMediaDir from database.
+            MediaStore.scanFile(cr, directoryNoMedia);
+
+            // Query for all images/videos in the device. This shouldn't list imageInNoMediaDir
+            assertThat(getImageAndVideoFilesFromDatabase())
+                    .doesNotContain(imageInNoMediaDir.getName());
+
+            // Rename shouldn't throw error even if imageInNoMediaDir is not in database.
+            assertThat(imageInNoMediaDir.renameTo(renamedImageInDCIM)).isTrue();
+            // We can insert renamedImageInDCIM to database
+            ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.DATA, renamedImageInDCIM.getAbsolutePath());
+            final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values, null);
+            assertNotNull(uri);
+        } finally {
+            imageInNoMediaDir.delete();
+            renamedImageInDCIM.delete();
+            MediaStore.scanFile(cr, renamedImageInDCIM);
+            noMediaFile.delete();
+        }
+
+    }
+
     private static void assertCanCreateFile(File file) throws IOException {
         if (file.exists()) {
             file.delete();
@@ -489,4 +632,27 @@
             dir.delete();
         }
     }
+
+    /**
+     * Queries {@link ContentResolver} for all image and video files, returns display name of
+     * corresponding files.
+     */
+    private static ArrayList<String> getImageAndVideoFilesFromDatabase() {
+        ArrayList<String> mediaFiles = new ArrayList<>();
+        final String selection = "is_pending = 0 AND is_trashed = 0 AND "
+                + "(media_type = ? OR media_type = ?)";
+        final String[] selectionArgs = new String[] {
+                String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
+                String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)};
+
+        try (Cursor c = getContentResolver().query(
+                MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
+                /* projection */ new String[]{MediaStore.MediaColumns.DISPLAY_NAME},
+                selection, selectionArgs, null)) {
+            while (c.moveToNext()) {
+                mediaFiles.add(c.getString(0));
+            }
+        }
+        return mediaFiles;
+    }
 }
diff --git a/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java b/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java
index 89863ed..75735a5 100644
--- a/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java
+++ b/tests/jni/FuseDaemonTest/libs/FuseDaemonTestLib/src/com/android/tests/fused/lib/TestUtils.java
@@ -43,7 +43,9 @@
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.provider.MediaStore;
+import android.system.ErrnoException;
 import android.system.Os;
+import android.system.OsConstants;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -58,7 +60,10 @@
 import com.google.common.io.ByteStreams;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.concurrent.CountDownLatch;
@@ -76,6 +81,13 @@
     public static final String CREATE_FILE_QUERY = "com.android.tests.fused.createfile";
     public static final String DELETE_FILE_QUERY = "com.android.tests.fused.deletefile";
 
+
+    public static final String STR_DATA1 = "Just some random text";
+    public static final String STR_DATA2 = "More arbitrary stuff";
+
+    public static final byte[] BYTES_DATA1 = STR_DATA1.getBytes();
+    public static final byte[] BYTES_DATA2 = STR_DATA2.getBytes();
+
     private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
     private static final long POLLING_SLEEP_MILLIS = 100;
 
@@ -467,6 +479,32 @@
     }
 
     /**
+     * Asserts the entire content of the file equals exactly {@code expectedContent}.
+     */
+    public static void assertFileContent(File file, byte[] expectedContent) throws IOException {
+        try (final FileInputStream fis = new FileInputStream(file)) {
+            assertInputStreamContent(fis, expectedContent);
+        }
+    }
+
+    /**
+     * Asserts the entire content of the file equals exactly {@code expectedContent}.
+     * <p>Sets {@code fd} to beginning of file first.
+     */
+    public static void assertFileContent(FileDescriptor fd, byte[] expectedContent)
+            throws IOException, ErrnoException {
+        Os.lseek(fd, 0, OsConstants.SEEK_SET);
+        try (final FileInputStream fis = new FileInputStream(fd)) {
+            assertInputStreamContent(fis, expectedContent);
+        }
+    }
+
+    private static void assertInputStreamContent(InputStream in, byte[] expectedContent)
+            throws IOException {
+        assertThat(ByteStreams.toByteArray(in)).isEqualTo(expectedContent);
+    }
+
+    /**
      * Checks if the given {@code permission} is granted and corresponding AppOp is MODE_ALLOWED.
      */
     private static boolean checkPermissionAndAppOp(String permission) {
diff --git a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
index a17e7b9..54caffe 100644
--- a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
+++ b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
@@ -25,12 +25,16 @@
 import static com.android.tests.fused.lib.RedactionTestHelper.assertExifMetadataMismatch;
 import static com.android.tests.fused.lib.RedactionTestHelper.getExifMetadata;
 import static com.android.tests.fused.lib.RedactionTestHelper.getExifMetadataFromRawResource;
+import static com.android.tests.fused.lib.TestUtils.BYTES_DATA1;
+import static com.android.tests.fused.lib.TestUtils.BYTES_DATA2;
+import static com.android.tests.fused.lib.TestUtils.STR_DATA1;
+import static com.android.tests.fused.lib.TestUtils.STR_DATA2;
 import static com.android.tests.fused.lib.TestUtils.assertCanRenameFile;
 import static com.android.tests.fused.lib.TestUtils.assertCanRenameDirectory;
-import static com.android.tests.fused.lib.TestUtils.adoptShellPermissionIdentity;
 import static com.android.tests.fused.lib.TestUtils.allowAppOpsToUid;
 import static com.android.tests.fused.lib.TestUtils.assertCantRenameDirectory;
 import static com.android.tests.fused.lib.TestUtils.assertCantRenameFile;
+import static com.android.tests.fused.lib.TestUtils.assertFileContent;
 import static com.android.tests.fused.lib.TestUtils.assertThrows;
 import static com.android.tests.fused.lib.TestUtils.createFileAs;
 import static com.android.tests.fused.lib.TestUtils.deleteFileAs;
@@ -38,7 +42,6 @@
 import static com.android.tests.fused.lib.TestUtils.deleteRecursively;
 import static com.android.tests.fused.lib.TestUtils.deleteWithMediaProvider;
 import static com.android.tests.fused.lib.TestUtils.denyAppOpsToUid;
-import static com.android.tests.fused.lib.TestUtils.dropShellPermissionIdentity;
 import static com.android.tests.fused.lib.TestUtils.executeShellCommand;
 import static com.android.tests.fused.lib.TestUtils.getContentResolver;
 import static com.android.tests.fused.lib.TestUtils.getFileMimeTypeFromDatabase;
@@ -79,8 +82,6 @@
 import com.android.cts.install.lib.TestApp;
 import com.android.tests.fused.lib.ReaddirTestHelper;
 
-import com.google.common.io.ByteStreams;
-
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -104,13 +105,22 @@
 
     static final File EXTERNAL_STORAGE_DIR = Environment.getExternalStorageDirectory();
 
+    // Default top-level directories
+    static final File ALARMS_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_ALARMS);
+    static final File AUDIOBOOKS_DIR = new File(EXTERNAL_STORAGE_DIR,
+            Environment.DIRECTORY_AUDIOBOOKS);
     static final File DCIM_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_DCIM);
-    static final File PICTURES_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_PICTURES);
-    static final File MUSIC_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_MUSIC);
-    static final File MOVIES_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_MOVIES);
+    static final File DOCUMENTS_DIR = new File(EXTERNAL_STORAGE_DIR,
+            Environment.DIRECTORY_DOCUMENTS);
     static final File DOWNLOAD_DIR = new File(EXTERNAL_STORAGE_DIR,
             Environment.DIRECTORY_DOWNLOADS);
+    static final File MUSIC_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_MUSIC);
+    static final File MOVIES_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_MOVIES);
+    static final File NOTIFICATIONS_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_NOTIFICATIONS);
+    static final File PICTURES_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_PICTURES);
     static final File PODCASTS_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_PODCASTS);
+    static final File RINGTONES_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_RINGTONES);
+
     static final File ANDROID_DATA_DIR = new File(EXTERNAL_STORAGE_DIR, "Android/data");
     static final File ANDROID_MEDIA_DIR = new File(EXTERNAL_STORAGE_DIR, "Android/media");
     static final String TEST_DIRECTORY_NAME = "FilePathAccessTestDirectory";
@@ -118,17 +128,11 @@
     static final File EXTERNAL_FILES_DIR = getContext().getExternalFilesDir(null);
     static final File EXTERNAL_MEDIA_DIR = getContext().getExternalMediaDirs()[0];
 
-    static final String MUSIC_FILE_NAME = "FilePathAccessTest_file.mp3";
+    static final String AUDIO_FILE_NAME = "FilePathAccessTest_file.mp3";
     static final String VIDEO_FILE_NAME = "FilePathAccessTest_file.mp4";
     static final String IMAGE_FILE_NAME = "FilePathAccessTest_file.jpg";
     static final String NONMEDIA_FILE_NAME = "FilePathAccessTest_file.pdf";
 
-    static final String STR_DATA1 = "Just some random text";
-    static final String STR_DATA2 = "More arbitrary stuff";
-
-    static final byte[] BYTES_DATA1 = STR_DATA1.getBytes();
-    static final byte[] BYTES_DATA2 = STR_DATA2.getBytes();
-
     static final String FILE_CREATION_ERROR_MESSAGE = "No such file or directory";
 
     private static final TestApp TEST_APP_A  = new TestApp("TestAppA",
@@ -154,7 +158,7 @@
      */
     @Test
     public void testTypePathConformity() throws Exception {
-        // Only music files can be created in Music
+        // Only audio files can be created in Music
         assertThrows(IOException.class, "Operation not permitted", () -> {
             new File(MUSIC_DIR, NONMEDIA_FILE_NAME).createNewFile();
         });
@@ -169,7 +173,7 @@
             new File(MOVIES_DIR, NONMEDIA_FILE_NAME).createNewFile();
         });
         assertThrows(IOException.class, "Operation not permitted", () -> {
-            new File(MOVIES_DIR, MUSIC_FILE_NAME).createNewFile();
+            new File(MOVIES_DIR, AUDIO_FILE_NAME).createNewFile();
         });
         assertThrows(IOException.class, "Operation not permitted", () -> {
             new File(MOVIES_DIR, IMAGE_FILE_NAME).createNewFile();
@@ -179,28 +183,42 @@
             new File(DCIM_DIR, NONMEDIA_FILE_NAME).createNewFile();
         });
         assertThrows(IOException.class, "Operation not permitted", () -> {
-            new File(DCIM_DIR, MUSIC_FILE_NAME).createNewFile();
+            new File(DCIM_DIR, AUDIO_FILE_NAME).createNewFile();
         });
         // Only image and video files can be created in Pictures
         assertThrows(IOException.class, "Operation not permitted", () -> {
             new File(PICTURES_DIR, NONMEDIA_FILE_NAME).createNewFile();
         });
         assertThrows(IOException.class, "Operation not permitted", () -> {
-            new File(PICTURES_DIR, MUSIC_FILE_NAME).createNewFile();
+            new File(PICTURES_DIR, AUDIO_FILE_NAME).createNewFile();
         });
 
+        assertCanCreateFile(new File(ALARMS_DIR, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(AUDIOBOOKS_DIR, AUDIO_FILE_NAME));
         assertCanCreateFile(new File(DCIM_DIR, IMAGE_FILE_NAME));
-        assertCanCreateFile(new File(MUSIC_DIR, MUSIC_FILE_NAME));
-        assertCanCreateFile(new File(MOVIES_DIR, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(DCIM_DIR, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(DOCUMENTS_DIR, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(DOCUMENTS_DIR, IMAGE_FILE_NAME));
+        assertCanCreateFile(new File(DOCUMENTS_DIR, NONMEDIA_FILE_NAME));
+        assertCanCreateFile(new File(DOCUMENTS_DIR, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(DOWNLOAD_DIR, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(DOWNLOAD_DIR, IMAGE_FILE_NAME));
         assertCanCreateFile(new File(DOWNLOAD_DIR, NONMEDIA_FILE_NAME));
+        assertCanCreateFile(new File(DOWNLOAD_DIR, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(MOVIES_DIR, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(MUSIC_DIR, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(NOTIFICATIONS_DIR, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(PICTURES_DIR, IMAGE_FILE_NAME));
         assertCanCreateFile(new File(PICTURES_DIR, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(PODCASTS_DIR, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(RINGTONES_DIR, AUDIO_FILE_NAME));
 
         // No file whatsoever can be created in the top level directory
         assertThrows(IOException.class, "Operation not permitted", () -> {
             new File(EXTERNAL_STORAGE_DIR, NONMEDIA_FILE_NAME).createNewFile();
         });
         assertThrows(IOException.class, "Operation not permitted", () -> {
-            new File(EXTERNAL_STORAGE_DIR, MUSIC_FILE_NAME).createNewFile();
+            new File(EXTERNAL_STORAGE_DIR, AUDIO_FILE_NAME).createNewFile();
         });
         assertThrows(IOException.class, "Operation not permitted", () -> {
             new File(EXTERNAL_STORAGE_DIR, IMAGE_FILE_NAME).createNewFile();
@@ -931,9 +949,9 @@
 
     @Test
     public void testSystemGalleryAppHasNoFullAccessToAudio() throws Exception {
-        final File otherAppAudioFile = new File(MUSIC_DIR, "other_" + MUSIC_FILE_NAME);
-        final File topLevelAudioFile = new File(EXTERNAL_STORAGE_DIR, MUSIC_FILE_NAME);
-        final File audioInAnObviouslyWrongPlace = new File(PICTURES_DIR, MUSIC_FILE_NAME);
+        final File otherAppAudioFile = new File(MUSIC_DIR, "other_" + AUDIO_FILE_NAME);
+        final File topLevelAudioFile = new File(EXTERNAL_STORAGE_DIR, AUDIO_FILE_NAME);
+        final File audioInAnObviouslyWrongPlace = new File(PICTURES_DIR, AUDIO_FILE_NAME);
 
         try {
             installApp(TEST_APP_A, false);
@@ -980,7 +998,7 @@
         final File imageFile = new File(PICTURES_DIR, IMAGE_FILE_NAME);
         final File videoFile = new File(PICTURES_DIR, VIDEO_FILE_NAME);
         final File topLevelVideoFile = new File(EXTERNAL_STORAGE_DIR, VIDEO_FILE_NAME);
-        final File musicFile = new File(MUSIC_DIR, MUSIC_FILE_NAME);
+        final File musicFile = new File(MUSIC_DIR, AUDIO_FILE_NAME);
         try {
             installApp(TEST_APP_A, false);
             allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
@@ -1262,7 +1280,7 @@
     @Test
     public void testManageExternalStorageCanCreateFilesAnywhere() throws Exception {
         final File topLevelPdf = new File(EXTERNAL_STORAGE_DIR, NONMEDIA_FILE_NAME);
-        final File musicFileInMovies = new File(MOVIES_DIR, MUSIC_FILE_NAME);
+        final File musicFileInMovies = new File(MOVIES_DIR, AUDIO_FILE_NAME);
         final File imageFileInDcim = new File(DCIM_DIR, IMAGE_FILE_NAME);
         try {
             allowAppOpsToUid(Process.myUid(), OPSTR_MANAGE_EXTERNAL_STORAGE);
@@ -1304,7 +1322,7 @@
     public void testManageExternalStorageCanDeleteOtherAppsContents() throws Exception {
         final File otherAppPdf = new File(DOWNLOAD_DIR, "other" + NONMEDIA_FILE_NAME);
         final File otherAppImage = new File(DCIM_DIR, "other" + IMAGE_FILE_NAME);
-        final File otherAppMusic = new File(MUSIC_DIR, "other" + MUSIC_FILE_NAME);
+        final File otherAppMusic = new File(MUSIC_DIR, "other" + AUDIO_FILE_NAME);
         try {
             installApp(TEST_APP_A, false);
 
@@ -1339,7 +1357,7 @@
         final File pdf = new File(DOWNLOAD_DIR, NONMEDIA_FILE_NAME);
         final File pdfInObviouslyWrongPlace = new File(PICTURES_DIR, NONMEDIA_FILE_NAME);
         final File topLevelPdf = new File(EXTERNAL_STORAGE_DIR, NONMEDIA_FILE_NAME);
-        final File musicFile = new File(MUSIC_DIR, MUSIC_FILE_NAME);
+        final File musicFile = new File(MUSIC_DIR, AUDIO_FILE_NAME);
         try {
             installApp(TEST_APP_A, false);
 
@@ -1398,7 +1416,7 @@
     public void testManageExternalStorageQueryOtherAppsFile() throws Exception {
         final File otherAppPdf = new File(DOWNLOAD_DIR, "other" + NONMEDIA_FILE_NAME);
         final File otherAppImg = new File(DCIM_DIR, "other" + IMAGE_FILE_NAME);
-        final File otherAppMusic = new File(MUSIC_DIR, "other" + MUSIC_FILE_NAME);
+        final File otherAppMusic = new File(MUSIC_DIR, "other" + AUDIO_FILE_NAME);
         try {
             installApp(TEST_APP_A, false);
             assertCreateFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
@@ -1421,7 +1439,7 @@
     public void testQueryOtherAppsFiles() throws Exception {
         final File otherAppPdf = new File(DOWNLOAD_DIR, "other" + NONMEDIA_FILE_NAME);
         final File otherAppImg = new File(DCIM_DIR, "other" + IMAGE_FILE_NAME);
-        final File otherAppMusic = new File(MUSIC_DIR, "other" + MUSIC_FILE_NAME);
+        final File otherAppMusic = new File(MUSIC_DIR, "other" + AUDIO_FILE_NAME);
         try {
             installApp(TEST_APP_A, false);
             assertCreateFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
@@ -1441,7 +1459,7 @@
     public void testSystemGalleryQueryOtherAppsFiles() throws Exception {
         final File otherAppPdf = new File(DOWNLOAD_DIR, "other" + NONMEDIA_FILE_NAME);
         final File otherAppImg = new File(DCIM_DIR, "other" + IMAGE_FILE_NAME);
-        final File otherAppMusic = new File(MUSIC_DIR, "other" + MUSIC_FILE_NAME);
+        final File otherAppMusic = new File(MUSIC_DIR, "other" + AUDIO_FILE_NAME);
         try {
             installApp(TEST_APP_A, false);
             assertCreateFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
@@ -1591,30 +1609,4 @@
                     + "running the test!");
         }
     }
-
-    /**
-     * Asserts the entire content of the file equals exactly {@code expectedContent}.
-     */
-    private static void assertFileContent(File file, byte[] expectedContent) throws IOException {
-        try (final FileInputStream fis = new FileInputStream(file)) {
-            assertInputStreamContent(fis, expectedContent);
-        }
-    }
-
-    /**
-     * Asserts the entire content of the file equals exactly {@code expectedContent}.
-     * <p>Sets {@code fd} to beginning of file first.
-     */
-    private static void assertFileContent(FileDescriptor fd, byte[] expectedContent)
-            throws IOException, ErrnoException {
-        Os.lseek(fd, 0, OsConstants.SEEK_SET);
-        try (final FileInputStream fis = new FileInputStream(fd)) {
-            assertInputStreamContent(fis, expectedContent);
-        }
-    }
-
-    private static void assertInputStreamContent(InputStream in, byte[] expectedContent)
-            throws IOException {
-        assertThat(ByteStreams.toByteArray(in)).isEqualTo(expectedContent);
-    }
 }
diff --git a/tests/src/com/android/providers/media/DatabaseHelperTest.java b/tests/src/com/android/providers/media/DatabaseHelperTest.java
index 5923573..a655fe6 100644
--- a/tests/src/com/android/providers/media/DatabaseHelperTest.java
+++ b/tests/src/com/android/providers/media/DatabaseHelperTest.java
@@ -426,7 +426,7 @@
     private static class DatabaseHelperO extends DatabaseHelper {
         public DatabaseHelperO(Context context, String name) {
             super(context, name, DatabaseHelper.VERSION_O,
-                    false, false, true, Column.class, null, null);
+                    false, false, true, Column.class, null, null, null);
         }
 
         @Override
@@ -438,7 +438,7 @@
     private static class DatabaseHelperP extends DatabaseHelper {
         public DatabaseHelperP(Context context, String name) {
             super(context, name, DatabaseHelper.VERSION_P,
-                    false, false, true, Column.class, null, null);
+                    false, false, true, Column.class, null, null, null);
         }
 
         @Override
@@ -450,7 +450,7 @@
     private static class DatabaseHelperQ extends DatabaseHelper {
         public DatabaseHelperQ(Context context, String name) {
             super(context, name, DatabaseHelper.VERSION_Q,
-                    false, false, true, Column.class, null, null);
+                    false, false, true, Column.class, null, null, null);
         }
 
         @Override
@@ -462,7 +462,7 @@
     private static class DatabaseHelperR extends DatabaseHelper {
         public DatabaseHelperR(Context context, String name) {
             super(context, name, DatabaseHelper.VERSION_R,
-                    false, false, true, Column.class, null, null);
+                    false, false, true, Column.class, null, null, null);
         }
     }