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);
}
}