Add additional tests for pending files from FUSE
Added new tests to verify:
1. Owned pending files from FUSE are visible when queried with
MATCH_EXCLUDE.
2. Apps can always open other apps pending files from FUSE via filePath
or via MediaStore APIs
3. Legacy apps creating file for existing db row doesn't upsert and set
IS_PENDING=1.
Bug: 158637603
Test: atest CtsScopedStorageHostTest#testCreateDoesntUpsert_hasRW
Test: atest CtsScopedStorageHostTest#testPendingFromFuse
Test: atest CtsScopedStorageHostTest#testOpenPendingFilesFromFuse
Change-Id: I848158d90bfc13491952a2f6577f87b1af4e50f6
Merged-In: I848158d90bfc13491952a2f6577f87b1af4e50f6
(cherry picked from commit d765f7da5c380428ad0ebb83b7b72c6f8973244d)
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
index 4ca2e12..ab391c9 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
@@ -178,6 +178,11 @@
}
@Test
+ public void testCreateDoesntUpsert() throws Exception {
+ runDeviceTest("testCreateDoesntUpsert");
+ }
+
+ @Test
public void testCaseInsensitivity() throws Exception {
runDeviceTest("testAndroidDataObbCannotBeDeleted");
}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index 88be97c..d88329f6 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -352,6 +352,16 @@
}
@Test
+ public void testOpenOtherPendingFilesFromFuse() throws Exception {
+ grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
+ try {
+ runDeviceTest("testOpenOtherPendingFilesFromFuse");
+ } finally {
+ revokePermissions("android.permission.READ_EXTERNAL_STORAGE");
+ }
+ }
+
+ @Test
public void testAccess_file() throws Exception {
grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
try {
diff --git a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
index 251f65a..43c6d20 100644
--- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
+++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
@@ -27,6 +27,7 @@
import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
@@ -617,6 +618,35 @@
}
}
+ /**
+ * Test that legacy apps creating files for existing db row doesn't upsert and set IS_PENDING
+ */
+ @Test
+ public void testCreateDoesntUpsert() throws Exception {
+ pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
+ pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+ final File file = new File(TestUtils.getDcimDir(), IMAGE_FILE_NAME);
+ Uri uri = null;
+ try {
+ uri = TestUtils.insertFileUsingDataColumn(file);
+ assertNotNull(uri);
+
+ assertTrue(file.createNewFile());
+
+ try (Cursor c = TestUtils.queryFile(file,
+ new String[] {MediaStore.MediaColumns.IS_PENDING})) {
+ // This file will not have IS_PENDING=1 because create didn't set IS_PENDING.
+ assertTrue(c.moveToFirst());
+ assertEquals(c.getInt(0), 0);
+ }
+ } finally {
+ file.delete();
+ // If create file fails, we should delete the inserted db row.
+ deleteWithMediaProviderNoThrow(uri);
+ }
+ }
+
@Test
public void testAndroidDataObbCannotBeDeleted() throws Exception {
File canDeleteDir = new File("/sdcard/canDelete");
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
index 22e6fb4..3a2caaf 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
@@ -406,7 +406,7 @@
@NonNull
public static Cursor queryVideoFile(File file, String... projection) {
return queryFile(MediaStore.Video.Media.getContentUri(sStorageVolumeName), file,
- projection);
+ /*includePending*/ true, projection);
}
/**
@@ -416,7 +416,7 @@
@NonNull
public static Cursor queryImageFile(File file, String... projection) {
return queryFile(MediaStore.Images.Media.getContentUri(sStorageVolumeName), file,
- projection);
+ /*includePending*/ true, projection);
}
/**
@@ -991,22 +991,38 @@
}
}
+ /**
+ * Queries {@link ContentResolver} for a file IS_PENDING=0 and returns a {@link Cursor} with the
+ * given columns.
+ */
@NonNull
- public static Cursor queryFile(@NonNull File file, String... projection) {
- return queryFile(
- MediaStore.Files.getContentUri(sStorageVolumeName), file, projection);
+ public static Cursor queryFileExcludingPending(@NonNull File file, String... projection) {
+ return queryFile(MediaStore.Files.getContentUri(sStorageVolumeName), file,
+ /*includePending*/ false, projection);
}
@NonNull
- private static Cursor queryFile(@NonNull Uri uri, @NonNull File file, String... projection) {
+ public static Cursor queryFile(@NonNull File file, String... projection) {
+ return queryFile(MediaStore.Files.getContentUri(sStorageVolumeName), file,
+ /*includePending*/ true, projection);
+ }
+
+ @NonNull
+ private static Cursor queryFile(@NonNull Uri uri, @NonNull File file, boolean includePending,
+ String... projection) {
Bundle queryArgs = new Bundle();
queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
MediaStore.MediaColumns.DATA + " = ?");
queryArgs.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS,
new String[] { file.getAbsolutePath() });
- queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_PENDING, MediaStore.MATCH_INCLUDE);
queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_INCLUDE);
+ if (includePending) {
+ queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_PENDING, MediaStore.MATCH_INCLUDE);
+ } else {
+ queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_PENDING, MediaStore.MATCH_EXCLUDE);
+ }
+
final Cursor c = getContentResolver().query(uri, projection, queryArgs, null);
assertThat(c).isNotNull();
return c;
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index 1842d1c..878a82c 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -78,6 +78,7 @@
import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
import static android.scopedstorage.cts.lib.TestUtils.queryFile;
+import static android.scopedstorage.cts.lib.TestUtils.queryFileExcludingPending;
import static android.scopedstorage.cts.lib.TestUtils.queryImageFile;
import static android.scopedstorage.cts.lib.TestUtils.queryVideoFile;
import static android.scopedstorage.cts.lib.TestUtils.readExifMetadataFromTestApp;
@@ -2439,6 +2440,7 @@
@Test
public void testPendingFromFuse() throws Exception {
final File pendingFile = new File(getDcimDir(), IMAGE_FILE_NAME);
+ final File otherPendingFile = new File(getDcimDir(), VIDEO_FILE_NAME);
try {
assertTrue(pendingFile.createNewFile());
// Newly created file should have IS_PENDING set
@@ -2447,6 +2449,13 @@
assertThat(c.getInt(0)).isEqualTo(1);
}
+ // If we query with MATCH_EXCLUDE, we should still see this pendingFile
+ try (Cursor c = queryFileExcludingPending(pendingFile, MediaColumns.IS_PENDING)) {
+ assertThat(c.getCount()).isEqualTo(1);
+ assertTrue(c.moveToFirst());
+ assertThat(c.getInt(0)).isEqualTo(1);
+ }
+
assertNotNull(MediaStore.scanFile(getContentResolver(), pendingFile));
// IS_PENDING should be unset after the scan
@@ -2454,8 +2463,35 @@
assertTrue(c.moveToFirst());
assertThat(c.getInt(0)).isEqualTo(0);
}
+
+ installAppWithStoragePermissions(TEST_APP_A);
+ assertCreateFilesAs(TEST_APP_A, otherPendingFile);
+ // We can't query other apps pending file from FUSE with MATCH_EXCLUDE
+ try (Cursor c = queryFileExcludingPending(otherPendingFile, MediaColumns.IS_PENDING)) {
+ assertThat(c.getCount()).isEqualTo(0);
+ }
} finally {
pendingFile.delete();
+ deleteFileAsNoThrow(TEST_APP_A, otherPendingFile.getAbsolutePath());
+ uninstallAppNoThrow(TEST_APP_A);
+ }
+ }
+
+ @Test
+ public void testOpenOtherPendingFilesFromFuse() throws Exception {
+ final File otherPendingFile = new File(getDcimDir(), IMAGE_FILE_NAME);
+ try {
+ installApp(TEST_APP_A);
+ assertCreateFilesAs(TEST_APP_A, otherPendingFile);
+
+ // We can read other app's pending file from FUSE via filePath
+ assertCanQueryAndOpenFile(otherPendingFile, "r");
+
+ // We can also read other app's pending file via MediaStore API
+ assertNotNull(openWithMediaProvider(otherPendingFile, "r"));
+ } finally {
+ deleteFileAsNoThrow(TEST_APP_A, otherPendingFile.getAbsolutePath());
+ uninstallAppNoThrow(TEST_APP_A);
}
}