Merge "Get "ForFuse" methods some test coverage." into rvc-dev
diff --git a/Android.bp b/Android.bp
index b7092bc..34acc32 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12,6 +12,9 @@
     libs: [
         "unsupportedappusage",
         "app-compat-annotations",
+        "framework-mediaprovider",
+        "framework_mediaprovider_annotation",
+        "framework-statsd-stubs-module_libs_api",
     ],
 
     jni_libs: [
@@ -26,10 +29,6 @@
         ":mediaprovider-sources",
     ],
 
-    // Rewrite our hidden API usage of MediaStore to avoid "Inlined method
-    // resolution crossed dex file boundary" errors
-    jarjar_rules: "jarjar-rules.txt",
-
     optimize: {
         proguard_flags_files: ["proguard.flags"],
     },
@@ -38,7 +37,7 @@
         "java_api_finder",
     ],
 
-    sdk_version: "system_current",
+    sdk_version: "module_current",
 
     certificate: "media",
     privileged: true,
@@ -46,15 +45,12 @@
     aaptflags: ["--custom-package com.android.providers.media"],
 }
 
-// This is defined to give MediaProviderTests all the source it needs to
-// run its tests against
+// Used by MediaProvider and MediaProviderTests
 filegroup {
     name: "mediaprovider-sources",
     srcs: [
         "src/**/*.aidl",
         "src/**/*.java",
-        ":framework-mediaprovider-sources",
-        ":framework-mediaprovider-annotation-sources",
         ":mediaprovider-database-sources",
         ":statslog-mediaprovider-java-gen",
     ],
diff --git a/apex/framework/Android.bp b/apex/framework/Android.bp
index f5747d5..7cb77f9 100644
--- a/apex/framework/Android.bp
+++ b/apex/framework/Android.bp
@@ -34,7 +34,7 @@
     plugins: ["java_api_finder"],
 
     hostdex: true, // for hiddenapi check
-    visibility: ["//packages/providers/MediaProvider/apex:__subpackages__"],
+    visibility: ["//packages/providers/MediaProvider:__subpackages__"],
     apex_available: [
         "com.android.mediaprovider",
         "test_com.android.mediaprovider",
diff --git a/jarjar-rules.txt b/jarjar-rules.txt
deleted file mode 100644
index a16c1ab..0000000
--- a/jarjar-rules.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-rule android.provider.Column* com.android.providers.media.framework.Column@1
-rule android.provider.MediaStore* com.android.providers.media.framework.MediaStore@1
diff --git a/legacy/src/com/android/providers/media/LegacyMediaProvider.java b/legacy/src/com/android/providers/media/LegacyMediaProvider.java
index 488e72a..086f88c 100644
--- a/legacy/src/com/android/providers/media/LegacyMediaProvider.java
+++ b/legacy/src/com/android/providers/media/LegacyMediaProvider.java
@@ -73,9 +73,9 @@
         Logging.initPersistent(persistentDir);
 
         mInternalDatabase = new DatabaseHelper(context, INTERNAL_DATABASE_NAME,
-                true, false, true, null, null, null, null);
+                true, false, true, null, null, null, null, null);
         mExternalDatabase = new DatabaseHelper(context, EXTERNAL_DATABASE_NAME,
-                false, false, true, null, null, null, null);
+                false, false, true, null, null, null, null, null);
 
         return true;
     }
diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java
index 20b6423..4f9528e 100644
--- a/src/com/android/providers/media/DatabaseHelper.java
+++ b/src/com/android/providers/media/DatabaseHelper.java
@@ -70,11 +70,13 @@
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 import java.util.function.LongSupplier;
+import java.util.function.UnaryOperator;
 import java.util.regex.Matcher;
 
 /**
@@ -104,6 +106,7 @@
     final @Nullable OnSchemaChangeListener mSchemaListener;
     final @Nullable OnFilesChangeListener mFilesListener;
     final @Nullable OnLegacyMigrationListener mMigrationListener;
+    final @Nullable UnaryOperator<String> mIdGenerator;
     final Set<String> mFilterVolumeNames = new ArraySet<>();
     long mScanStartTime;
     long mScanStopTime;
@@ -118,11 +121,12 @@
     public interface OnFilesChangeListener {
         public void onInsert(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
                 int mediaType, boolean isDownload);
-        public void onUpdate(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
-                int oldMediaType, boolean oldIsDownload,
-                int newMediaType, boolean newIsDownload);
+        public void onUpdate(@NonNull DatabaseHelper helper, @NonNull String volumeName,
+                long oldId, int oldMediaType, boolean oldIsDownload,
+                long newId, int newMediaType, boolean newIsDownload,
+                String ownerPackage, String oldPath);
         public void onDelete(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
-                int mediaType, boolean isDownload);
+                int mediaType, boolean isDownload, String ownerPackage, String path);
     }
 
     public interface OnLegacyMigrationListener {
@@ -135,9 +139,10 @@
             @Nullable Class<? extends Annotation> columnAnnotation,
             @Nullable OnSchemaChangeListener schemaListener,
             @Nullable OnFilesChangeListener filesListener,
-            @Nullable OnLegacyMigrationListener migrationListener) {
+            @Nullable OnLegacyMigrationListener migrationListener,
+            @Nullable UnaryOperator<String> idGenerator) {
         this(context, name, getDatabaseVersion(context), internal, earlyUpgrade, legacyProvider,
-                columnAnnotation, schemaListener, filesListener, migrationListener);
+                columnAnnotation, schemaListener, filesListener, migrationListener, idGenerator);
     }
 
     public DatabaseHelper(Context context, String name, int version,
@@ -145,7 +150,8 @@
             @Nullable Class<? extends Annotation> columnAnnotation,
             @Nullable OnSchemaChangeListener schemaListener,
             @Nullable OnFilesChangeListener filesListener,
-            @Nullable OnLegacyMigrationListener migrationListener) {
+            @Nullable OnLegacyMigrationListener migrationListener,
+            @Nullable UnaryOperator<String> idGenerator) {
         super(context, name, null, version);
         mContext = context;
         mName = name;
@@ -158,6 +164,7 @@
         mSchemaListener = schemaListener;
         mFilesListener = filesListener;
         mMigrationListener = migrationListener;
+        mIdGenerator = idGenerator;
 
         // Configure default filters until we hear differently
         if (mInternal) {
@@ -236,16 +243,21 @@
             if (arg != null && mFilesListener != null && !mSchemaChanging) {
                 final String[] split = arg.split(":");
                 final String volumeName = split[0];
-                final long id = Long.parseLong(split[1]);
+                final long oldId = Long.parseLong(split[1]);
                 final int oldMediaType = Integer.parseInt(split[2]);
                 final boolean oldIsDownload = Integer.parseInt(split[3]) != 0;
-                final int newMediaType = Integer.parseInt(split[4]);
-                final boolean newIsDownload = Integer.parseInt(split[5]) != 0;
+                final long newId = Long.parseLong(split[4]);
+                final int newMediaType = Integer.parseInt(split[5]);
+                final boolean newIsDownload = Integer.parseInt(split[6]) != 0;
+                final String ownerPackage = split[7];
+                // Path can include ':',  assume rest of split[8..length] is path.
+                final String oldPath = String.join(":", Arrays.copyOfRange(split, 8, split.length));
 
                 Trace.beginSection("_UPDATE");
                 try {
-                    mFilesListener.onUpdate(DatabaseHelper.this, volumeName, id,
-                            oldMediaType, oldIsDownload, newMediaType, newIsDownload);
+                    mFilesListener.onUpdate(DatabaseHelper.this, volumeName, oldId,
+                            oldMediaType, oldIsDownload, newId, newMediaType, newIsDownload,
+                            ownerPackage, oldPath);
                 } finally {
                     Trace.endSection();
                 }
@@ -259,11 +271,25 @@
                 final long id = Long.parseLong(split[1]);
                 final int mediaType = Integer.parseInt(split[2]);
                 final boolean isDownload = Integer.parseInt(split[3]) != 0;
+                final String ownerPackage = split[4];
+                // Path can include ':',  assume rest of split[5..length] is path.
+                final String path = String.join(":", Arrays.copyOfRange(split, 5, split.length));
 
                 Trace.beginSection("_DELETE");
                 try {
                     mFilesListener.onDelete(DatabaseHelper.this, volumeName, id,
-                            mediaType, isDownload);
+                            mediaType, isDownload, ownerPackage, path);
+                } finally {
+                    Trace.endSection();
+                }
+            }
+            return null;
+        });
+        db.setCustomScalarFunction("_GET_ID", (arg) -> {
+            if (mIdGenerator != null && !mSchemaChanging) {
+                Trace.beginSection("_GET_ID");
+                try {
+                    return mIdGenerator.apply(arg);
                 } finally {
                     Trace.endSection();
                 }
@@ -878,9 +904,11 @@
                 "new.volume_name||':'||new._id||':'||new.media_type||':'||new.is_download";
         final String updateArg =
                 "old.volume_name||':'||old._id||':'||old.media_type||':'||old.is_download"
-                        + "||':'||new.media_type||':'||new.is_download";
+                        + "||':'||new._id||':'||new.media_type||':'||new.is_download"
+                        + "||':'||ifnull(old.owner_package_name,'null')||':'||old._data";
         final String deleteArg =
-                "old.volume_name||':'||old._id||':'||old.media_type||':'||old.is_download";
+                "old.volume_name||':'||old._id||':'||old.media_type||':'||old.is_download"
+                        + "||':'||ifnull(old.owner_package_name,'null')||':'||old._data";
 
         db.execSQL("CREATE TRIGGER files_insert AFTER INSERT ON files"
                 + " BEGIN SELECT _INSERT(" + insertArg + "); END");
@@ -1135,7 +1163,7 @@
     static final int VERSION_O = 800;
     static final int VERSION_P = 900;
     static final int VERSION_Q = 1023;
-    static final int VERSION_R = 1112;
+    static final int VERSION_R = 1113;
     static final int VERSION_LATEST = VERSION_R;
 
     /**
@@ -1272,6 +1300,9 @@
             if (fromVersion < 1112) {
                 updateAddXmp(db, internal);
             }
+            if (fromVersion < 1113) {
+                // Empty version bump to ensure triggers are recreated
+            }
 
             if (recomputeDataValues) {
                 recomputeDataValues(db, internal);
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index f767dcd..fb1e326 100644
--- a/src/com/android/providers/media/LocalCallingIdentity.java
+++ b/src/com/android/providers/media/LocalCallingIdentity.java
@@ -47,6 +47,9 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.ArrayMap;
+
+import androidx.annotation.NonNull;
 
 import com.android.providers.media.util.LongArray;
 
@@ -76,6 +79,8 @@
      */
     private static final long FORCE_ENABLE_SCOPED_STORAGE = 132649864L;
 
+    private static final long UNKNOWN_ROW_ID = -1;
+
     public static LocalCallingIdentity fromBinder(Context context, ContentProvider provider) {
         String callingPackage = provider.getCallingPackageUnchecked();
         if (callingPackage == null) {
@@ -335,4 +340,22 @@
             }
         }
     }
+
+    private ArrayMap<String, Long> rowIdOfDeletedPaths = new ArrayMap<>();
+
+    public void addDeletedRowId(@NonNull String path, long id) {
+        rowIdOfDeletedPaths.put(path, id);
+    }
+
+    public void removeDeletedRowId(long id) {
+        int index = rowIdOfDeletedPaths.indexOfValue(id);
+        while (index > -1) {
+            rowIdOfDeletedPaths.removeAt(index);
+            index = rowIdOfDeletedPaths.indexOfValue(id);
+        }
+    }
+
+    public long getDeletedRowId(@NonNull String path) {
+        return rowIdOfDeletedPaths.getOrDefault(path, UNKNOWN_ROW_ID);
+    }
 }
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 6c2eadf..d48d8b9 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -208,6 +208,7 @@
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -523,6 +524,7 @@
         @Override
         public void onInsert(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
                 int mediaType, boolean isDownload) {
+            handleInsertedRowForFuse(id);
             acceptWithExpansion(helper::notifyInsert, volumeName, id, mediaType, isDownload);
 
             helper.postBackground(() -> {
@@ -538,16 +540,20 @@
         }
 
         @Override
-        public void onUpdate(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
-                int oldMediaType, boolean oldIsDownload,
-                int newMediaType, boolean newIsDownload) {
+        public void onUpdate(@NonNull DatabaseHelper helper, @NonNull String volumeName,
+                long oldId, int oldMediaType, boolean oldIsDownload,
+                long newId, int newMediaType, boolean newIsDownload,
+                String ownerPackage, String oldPath) {
             final boolean isDownload = oldIsDownload || newIsDownload;
-            acceptWithExpansion(helper::notifyUpdate, volumeName, id, oldMediaType, isDownload);
+            handleUpdatedRowForFuse(oldPath, ownerPackage, oldId, newId);
+            acceptWithExpansion(helper::notifyUpdate, volumeName, oldId, oldMediaType, isDownload);
+
             if (newMediaType != oldMediaType) {
-                acceptWithExpansion(helper::notifyUpdate, volumeName, id, newMediaType, isDownload);
+                acceptWithExpansion(helper::notifyUpdate, volumeName, oldId, newMediaType,
+                        isDownload);
 
                 helper.postBackground(() -> {
-                    final Uri fileUri = MediaStore.Files.getContentUri(volumeName, id);
+                    final Uri fileUri = MediaStore.Files.getContentUri(volumeName, oldId);
                     if (helper.isExternal()) {
                         // Update the quota type on the filesystem
                         updateQuotaTypeForUri(fileUri, newMediaType);
@@ -561,7 +567,8 @@
 
         @Override
         public void onDelete(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
-                int mediaType, boolean isDownload) {
+                int mediaType, boolean isDownload, String ownerPackageName, String path) {
+            handleDeletedRowForFuse(path, ownerPackageName, id);
             acceptWithExpansion(helper::notifyDelete, volumeName, id, mediaType, isDownload);
 
             helper.postBackground(() -> {
@@ -584,6 +591,14 @@
         }
     };
 
+    private final UnaryOperator<String> mIdGenerator = path -> {
+        final long rowId = mCallingIdentity.get().getDeletedRowId(path);
+        if (rowId != -1 && isFuseThread()) {
+            return String.valueOf(rowId);
+        }
+        return null;
+    };
+
     private final OnLegacyMigrationListener mMigrationListener = new OnLegacyMigrationListener() {
         @Override
         public void onStarted(ContentProviderClient client, String volumeName) {
@@ -787,10 +802,10 @@
 
         mInternalDatabase = new DatabaseHelper(context, INTERNAL_DATABASE_NAME,
                 true, false, mLegacyProvider, Column.class,
-                Metrics::logSchemaChange, mFilesListener, mMigrationListener);
+                Metrics::logSchemaChange, mFilesListener, mMigrationListener, mIdGenerator);
         mExternalDatabase = new DatabaseHelper(context, EXTERNAL_DATABASE_NAME,
                 false, false, mLegacyProvider, Column.class,
-                Metrics::logSchemaChange, mFilesListener, mMigrationListener);
+                Metrics::logSchemaChange, mFilesListener, mMigrationListener, mIdGenerator);
 
         final IntentFilter packageFilter = new IntentFilter();
         packageFilter.setPriority(10);
@@ -5093,6 +5108,42 @@
         values.put(MediaColumns.HEIGHT, bitmapOpts.outHeight);
     }
 
+    private void handleInsertedRowForFuse(long rowId) {
+        if (isFuseThread()) {
+            // Removes restored row ID saved list.
+            mCallingIdentity.get().removeDeletedRowId(rowId);
+        }
+    }
+
+    private void handleUpdatedRowForFuse(@NonNull String oldPath, @NonNull String ownerPackage,
+            long oldRowId, long newRowId) {
+        if (oldRowId == newRowId) {
+            // Update didn't delete or add row ID. We don't need to save row ID or remove saved
+            // deleted ID.
+            return;
+        }
+
+        handleDeletedRowForFuse(oldPath, ownerPackage, oldRowId);
+        handleInsertedRowForFuse(newRowId);
+    }
+
+    private void handleDeletedRowForFuse(@NonNull String path, @NonNull String ownerPackage,
+            long rowId) {
+        if (!isFuseThread()) {
+            return;
+        }
+
+        // Invalidate saved owned ID's of the previous owner of the deleted path, this prevents old
+        // owner from gaining access to newly created file with restored row ID.
+        if (!ownerPackage.equals("null") && !ownerPackage.equals(getCallingPackageOrSelf())) {
+            invalidateLocalCallingIdentityCache(ownerPackage, "owned_database_row_deleted:"
+                    + path);
+        }
+        // Saves row ID corresponding to deleted path. Saved row ID will be restored on subsequent
+        // create or rename.
+        mCallingIdentity.get().addDeletedRowId(path, rowId);
+    }
+
     /**
      * Return the {@link MediaColumns#DATA} field for the given {@code Uri}.
      */
diff --git a/src/com/android/providers/media/MediaUpgradeReceiver.java b/src/com/android/providers/media/MediaUpgradeReceiver.java
index dd99fc4..932b20b 100644
--- a/src/com/android/providers/media/MediaUpgradeReceiver.java
+++ b/src/com/android/providers/media/MediaUpgradeReceiver.java
@@ -69,7 +69,8 @@
                     try {
                         DatabaseHelper helper = new DatabaseHelper(
                                 context, file, MediaProvider.isInternalMediaDatabaseName(file),
-                                false, false, Column.class, Metrics::logSchemaChange, null, null);
+                                false, false, Column.class, Metrics::logSchemaChange, null, 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/util/SQLiteQueryBuilder.java b/src/com/android/providers/media/util/SQLiteQueryBuilder.java
index 990457c..da279aa 100644
--- a/src/com/android/providers/media/util/SQLiteQueryBuilder.java
+++ b/src/com/android/providers/media/util/SQLiteQueryBuilder.java
@@ -74,6 +74,18 @@
 
     private int mStrictFlags;
 
+    /**
+     * Raw SQL clause to obtain the value of {@link MediaColumns#_ID} from custom database function
+     * {@code _GET_ID} for INSERT operation.
+     */
+    private static final String GET_ID_FOR_INSERT_CLAUSE = "_GET_ID('%s')";
+
+    /**
+     * Raw SQL clause to obtain the value of {@link MediaColumns#_ID} from custom database function
+     * {@code _GET_ID} for UPDATE operation.
+     */
+    private static final String GET_ID_FOR_UPDATE_CLAUSE = "ifnull(_GET_ID('%s'), _id)";
+
     public SQLiteQueryBuilder() {
         mDistinct = false;
     }
@@ -838,6 +850,11 @@
             sql.append(',');
             sql.append(MediaColumns.GENERATION_MODIFIED);
         }
+        if (shouldAppendRowId(values)) {
+            sql.append(',');
+            sql.append(MediaColumns._ID);
+        }
+
         sql.append(") VALUES (");
         for (int i = 0; i < rawValues.size(); i++) {
             if (i > 0) {
@@ -855,6 +872,10 @@
             sql.append(DatabaseHelper.CURRENT_GENERATION_CLAUSE);
             sql.append(')');
         }
+        if (shouldAppendRowId(values)) {
+            sql.append(',');
+            sql.append(String.format(GET_ID_FOR_INSERT_CLAUSE, values.get(MediaColumns.DATA)));
+        }
         sql.append(")");
         return sql.toString();
     }
@@ -893,6 +914,12 @@
             sql.append(DatabaseHelper.CURRENT_GENERATION_CLAUSE);
             sql.append(')');
         }
+        if (shouldAppendRowId(values)) {
+            sql.append(',');
+            sql.append(MediaColumns._ID);
+            sql.append('=');
+            sql.append(String.format(GET_ID_FOR_UPDATE_CLAUSE, values.get(MediaColumns.DATA)));
+        }
 
         final String where = computeWhere(selection);
         appendClause(sql, " WHERE ", where);
@@ -1044,4 +1071,8 @@
             return "(" + arg + ")";
         }
     }
+
+    private static boolean shouldAppendRowId(ContentValues values) {
+        return !values.containsKey(MediaColumns._ID) && values.containsKey(MediaColumns.DATA);
+    }
 }
diff --git a/tests/Android.bp b/tests/Android.bp
index 5507e65..e74d9bb 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -17,6 +17,7 @@
         "res",
     ],
     srcs: [
+        ":framework-mediaprovider-sources",
         ":mediaprovider-sources",
         "src/**/*.java",
     ],
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 79cac4f..c8690c5 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
@@ -281,4 +281,14 @@
     public void testSystemGalleryCanRenameImageAndVideoDirs() throws Exception {
         runDeviceTest("testSystemGalleryCanRenameImageAndVideoDirs");
     }
+
+    @Test
+    public void testCreateCanRestoreDeletedRowId() throws Exception {
+        runDeviceTest("testCreateCanRestoreDeletedRowId");
+    }
+
+    @Test
+    public void testRenameCanRestoreDeletedRowId() throws Exception {
+        runDeviceTest("testRenameCanRestoreDeletedRowId");
+    }
 }
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 80df24f..3ab4c39 100644
--- a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
+++ b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
@@ -102,6 +102,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;
@@ -109,6 +111,7 @@
 
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -682,6 +685,7 @@
             assertThat(listAs(TEST_APP_A, EXTERNAL_MEDIA_DIR.getPath())).containsExactly(videoFileName);
         } finally {
             videoFile.delete();
+            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
@@ -1278,18 +1282,22 @@
     public void testRenameAndReplaceFile() throws Exception {
         final File videoFile1 = new File(DCIM_DIR, VIDEO_FILE_NAME);
         final File videoFile2 = new File(MOVIES_DIR, VIDEO_FILE_NAME);
+        final ContentResolver cr = getContentResolver();
         try {
             assertThat(videoFile1.createNewFile()).isTrue();
             assertThat(videoFile2.createNewFile()).isTrue();
-            final String[] projection = new String[] {MediaColumns._ID};
-            // Get id of video file in movies which will be deleted on rename.
-            final int id = getFileRowIdFromDatabase(videoFile2);
+            final Uri uriVideoFile1 = MediaStore.scanFile(cr, videoFile1);
+            final Uri uriVideoFile2 = MediaStore.scanFile(cr, videoFile2);
 
             // Renaming a file which replaces file in newPath videoFile2 is allowed.
             assertCanRenameFile(videoFile1, videoFile2);
 
-            // MediaProvider database entry for videoFile2 should be deleted on rename.
-            assertThat(getFileRowIdFromDatabase(videoFile2)).isNotEqualTo(id);
+            // Uri of videoFile2 should be accessible after rename.
+            assertThat(cr.openFileDescriptor(uriVideoFile2, "rw")).isNotNull();
+            // Uri of videoFile1 should not be accessible after rename.
+            assertThrows(FileNotFoundException.class, () -> {
+                cr.openFileDescriptor(uriVideoFile1, "rw");
+            });
         } finally {
             videoFile1.delete();
             videoFile2.delete();
@@ -1684,8 +1692,12 @@
             executeShellCommand("rm " + otherAppPdfFile2);
             otherAppImageFile1.delete();
             otherAppImageFile2.delete();
+            MediaStore.scanFile(getContentResolver(), otherAppImageFile1);
+            MediaStore.scanFile(getContentResolver(), otherAppImageFile2);
             otherAppVideoFile1.delete();
             otherAppVideoFile2.delete();
+            MediaStore.scanFile(getContentResolver(), otherAppVideoFile1);
+            MediaStore.scanFile(getContentResolver(), otherAppVideoFile2);
             otherAppPdfFile1.delete();
             otherAppPdfFile2.delete();
             dirInDcim.delete();
@@ -1695,6 +1707,71 @@
         }
     }
 
+    /**
+     * Test that row ID corresponding to deleted path is restored on subsequent create.
+     */
+    @Test
+    public void testCreateCanRestoreDeletedRowId() throws Exception {
+        final File imageFile = new File(DCIM_DIR, IMAGE_FILE_NAME);
+        final ContentResolver cr = getContentResolver();
+
+        try {
+            assertThat(imageFile.createNewFile()).isTrue();
+            final long oldRowId = getFileRowIdFromDatabase(imageFile);
+            assertThat(oldRowId).isNotEqualTo(-1);
+            final Uri uriOfOldFile = MediaStore.scanFile(cr, imageFile);
+            assertThat(uriOfOldFile).isNotNull();
+
+            assertThat(imageFile.delete()).isTrue();
+            // We should restore old row Id corresponding to deleted imageFile.
+            assertThat(imageFile.createNewFile()).isTrue();
+            assertThat(getFileRowIdFromDatabase(imageFile)).isEqualTo(oldRowId);
+            assertThat(cr.openFileDescriptor(uriOfOldFile, "rw")).isNotNull();
+
+            assertThat(imageFile.delete()).isTrue();
+            installApp(TEST_APP_A, false);
+            assertThat(createFileAs(TEST_APP_A, imageFile.getAbsolutePath())).isTrue();
+
+            final Uri uriOfNewFile = MediaStore.scanFile(getContentResolver(), imageFile);
+            assertThat(uriOfNewFile).isNotNull();
+            // We shouldn't restore deleted row Id if delete & create are called from different apps
+            assertThat(Integer.getInteger(uriOfNewFile.getLastPathSegment()))
+                    .isNotEqualTo(oldRowId);
+        } finally {
+            imageFile.delete();
+            deleteFileAsNoThrow(TEST_APP_A, imageFile.getAbsolutePath());
+            uninstallAppNoThrow(TEST_APP_A);
+        }
+    }
+
+    /**
+     * Test that row ID corresponding to deleted path is restored on subsequent rename.
+     */
+    @Test
+    public void testRenameCanRestoreDeletedRowId() throws Exception {
+        final File imageFile = new File(DCIM_DIR, IMAGE_FILE_NAME);
+        final File temporaryFile = new File(DOWNLOAD_DIR, IMAGE_FILE_NAME + "_.tmp");
+        final ContentResolver cr = getContentResolver();
+
+        try {
+            assertThat(imageFile.createNewFile()).isTrue();
+            final Uri oldUri = MediaStore.scanFile(cr, imageFile);
+            assertThat(oldUri).isNotNull();
+
+            Files.copy(imageFile, temporaryFile);
+            assertThat(imageFile.delete()).isTrue();
+            assertCanRenameFile(temporaryFile, imageFile);
+
+            final Uri newUri = MediaStore.scanFile(cr, imageFile);
+            assertThat(newUri).isNotNull();
+            assertThat(newUri.getLastPathSegment()).isEqualTo(oldUri.getLastPathSegment());
+            // oldUri of imageFile is still accessible after delete and rename.
+            assertThat(cr.openFileDescriptor(oldUri, "rw")).isNotNull();
+        } finally {
+            imageFile.delete();
+            temporaryFile.delete();
+        }
+    }
     private static void assertCantQueryFile(File file) {
         assertThat(getFileUri(file)).isNull();
     }
diff --git a/tests/src/com/android/providers/media/DatabaseHelperTest.java b/tests/src/com/android/providers/media/DatabaseHelperTest.java
index badff08..1149854 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, null);
+                    false, false, true, Column.class, null, null, null, null);
         }
 
         @Override
@@ -443,7 +443,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, null);
+                    false, false, true, Column.class, null, null, null, null);
         }
 
         @Override
@@ -460,7 +460,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, null);
+                    false, false, true, Column.class, null, null, null, null);
         }
 
         @Override
@@ -477,7 +477,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, null);
+                    false, false, true, Column.class, null, null, null, null);
         }
     }