Support hidden file deletion through file path.

Hidden file paths are sanitized before inserting into database. FUSE requests
to delete the file should also sanitize the file path before calling
MediaProvider delete. MediaProvider delete will not remove the file from
lower filesystem if actual path is different from sanitized path, delete
the file explicitly for this case.

Test: atest iFuseDaemonHostTest#testCanCreateHiddenFile
Bug: 148579340
Change-Id: If56a50d42553720b4cd66742f2de6918ce37e9bd
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index f5c93fe..0e2b5ea 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -5405,7 +5405,7 @@
      * Called from JNI in jni/MediaProviderWrapper.cpp
      */
     @Keep
-    public int deleteFileForFuse(@NonNull String path, int uid) {
+    public int deleteFileForFuse(@NonNull String path, int uid) throws IOException {
         final LocalCallingIdentity token =
                 clearLocalCallingIdentity(LocalCallingIdentity.fromExternal(getContext(), uid));
         try {
@@ -5432,12 +5432,21 @@
                 return OsConstants.EPERM;
             }
 
+            final String sanitizedPath = getAbsoluteSanitizedPath(path);
+            if (sanitizedPath == null) {
+                throw new IOException("Invalid path " + path);
+            }
+
             final Uri contentUri = Files.getContentUri(MediaStore.getVolumeName(new File(path)));
             final String where = FileColumns.DATA + " = ?";
-            final String[] whereArgs = {path};
+            final String[] whereArgs = {sanitizedPath};
 
             if (delete(contentUri, where, whereArgs) == 0) {
                 return OsConstants.ENOENT;
+            } else if (!path.equals(sanitizedPath)) {
+                // delete() doesn't delete the file in lower file system if sanitized path is
+                // different path from actual path. Delete the file using actual path of the file.
+                return deleteFileUnchecked(path);
             } else {
                 // success - 1 file was deleted
                 return 0;
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 fa4f338..dd02cc5 100644
--- a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
+++ b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
@@ -1266,19 +1266,9 @@
             }
             assertFileContent(hiddenFile, BYTES_DATA1);
             // We can delete hidden file
-            // TODO(b/148579340): Uncomment these once we support hidden file deletion.
-            // assertThat(hiddenFile.delete()).isTrue();
-            // assertThat(hiddenFile.exists()).isFalse();
+            assertThat(hiddenFile.delete()).isTrue();
+            assertThat(hiddenFile.exists()).isFalse();
         } finally {
-            // TODO(b/148579340): Remove below workaround once the hidden file deletion is
-            // supported.
-            executeShellCommand("rm " + hiddenFile.getAbsolutePath());
-            String selection = MediaColumns.RELATIVE_PATH + " = ? AND "
-                    + MediaColumns.DISPLAY_NAME + " = ?";
-            String[] selectionArgs = {hiddenFile.getParentFile().getName() + "/", "_.hiddenFile" };
-            getContentResolver().delete(MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
-                    selection, selectionArgs);
-
             hiddenFile.delete();
         }
     }