Merge "Delete database entry for files deleted by apps that bypass restrictions"
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index 282a088..d8c2f46 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -299,6 +299,7 @@
     std::recursive_mutex lock;
     const string path;
     node* const root;
+    struct fuse_session* se;
 
     /*
      * Used to make JNI calls to MediaProvider.
@@ -1540,6 +1541,31 @@
     return use_fuse;
 }
 
+void FuseDaemon::InvalidateFuseDentryCache(const std::string& path) {
+    TRACE_VERBOSE << "Invalidating dentry for path " << path;
+
+    if (active.load(std::memory_order_acquire)) {
+        string name;
+        fuse_ino_t parent;
+
+        {
+            std::lock_guard<std::recursive_mutex> guard(fuse->lock);
+            const node* node = node::LookupAbsolutePath(fuse->root, path);
+            if (node) {
+                name = node->GetName();
+                parent = fuse->ToInode(node->GetParent());
+            }
+        }
+
+        if (!name.empty() &&
+            fuse_lowlevel_notify_inval_entry(fuse->se, parent, name.c_str(), name.size())) {
+            LOG(ERROR) << "Failed to invalidate dentry for path " << path;
+        }
+    } else {
+        TRACE << "FUSE daemon is inactive. Cannot invalidate dentry for " << path;
+    }
+}
+
 FuseDaemon::FuseDaemon(JNIEnv* env, jobject mediaProvider) : mp(env, mediaProvider),
                                                              active(false), fuse(nullptr) {}
 
@@ -1594,6 +1620,7 @@
         PLOG(ERROR) << "Failed to create session ";
         return;
     }
+    fuse_default.se = se;
     se->fd = fd;
     se->mountpoint = strdup(path.c_str());
 
diff --git a/jni/FuseDaemon.h b/jni/FuseDaemon.h
index 1233863..1d25636 100644
--- a/jni/FuseDaemon.h
+++ b/jni/FuseDaemon.h
@@ -42,6 +42,11 @@
      */
     bool ShouldOpenWithFuse(int fd, bool for_read, const std::string& path);
 
+    /**
+     * Invalidate FUSE VFS dentry cache entry for path
+     */
+    void InvalidateFuseDentryCache(const std::string& path);
+
   private:
     FuseDaemon(const FuseDaemon&) = delete;
     void operator=(const FuseDaemon&) = delete;
diff --git a/jni/MediaProviderWrapper.cpp b/jni/MediaProviderWrapper.cpp
index 821dc8a..59588db 100644
--- a/jni/MediaProviderWrapper.cpp
+++ b/jni/MediaProviderWrapper.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "MediaProviderWrapper"
-
 #include "MediaProviderWrapper.h"
 #include "libfuse_jni/ReaddirHelper.h"
 
@@ -39,7 +37,6 @@
 namespace {
 
 constexpr const char* kPropRedactionEnabled = "persist.sys.fuse.redaction-enabled";
-constexpr const char* kPropShellBypass = "persist.sys.fuse.shell-bypass";
 
 constexpr uid_t ROOT_UID = 0;
 constexpr uid_t SHELL_UID = 2000;
@@ -47,7 +44,7 @@
 /** Private helper functions **/
 
 inline bool shouldBypassMediaProvider(uid_t uid) {
-    return (uid == SHELL_UID && GetBoolProperty(kPropShellBypass, false)) || uid == ROOT_UID;
+    return uid == SHELL_UID || uid == ROOT_UID;
 }
 
 bool CheckForJniException(JNIEnv* env) {
@@ -62,7 +59,6 @@
 std::unique_ptr<RedactionInfo> getRedactionInfoInternal(JNIEnv* env, jobject media_provider_object,
                                                         jmethodID mid_get_redaction_ranges,
                                                         uid_t uid, const string& path) {
-    LOG(DEBUG) << "Computing redaction ranges for uid = " << uid << " file = " << path;
     ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
     ScopedLongArrayRO redaction_ranges(
             env, static_cast<jlongArray>(env->CallObjectMethod(
@@ -78,11 +74,9 @@
         LOG(ERROR) << "Error while calculating redaction ranges: array length is uneven";
     } else if (redaction_ranges.size() > 0) {
         ri = std::make_unique<RedactionInfo>(redaction_ranges.size() / 2, redaction_ranges.get());
-        LOG(DEBUG) << "Redaction ranges computed. Number of ranges = " << ri->size();
     } else {
         // No ranges to redact
         ri = std::make_unique<RedactionInfo>();
-        LOG(DEBUG) << "Redaction ranges computed. No ranges to redact.";
     }
 
     return ri;
@@ -90,77 +84,72 @@
 
 int insertFileInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_insert_file,
                        const string& path, uid_t uid) {
-    LOG(DEBUG) << "Inserting file for UID = " << uid << ". Path = " << path;
     ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
     int res = env->CallIntMethod(media_provider_object, mid_insert_file, j_path.get(), uid);
 
     if (CheckForJniException(env)) {
-        LOG(DEBUG) << "Java exception while creating file";
         return EFAULT;
     }
-    LOG(DEBUG) << "res = " << res;
     return res;
 }
 
 int deleteFileInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_delete_file,
                        const string& path, uid_t uid) {
-    LOG(DEBUG) << "Delete file for UID = " << uid << ". Path = " << path;
     ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
     int res = env->CallIntMethod(media_provider_object, mid_delete_file, j_path.get(), uid);
 
     if (CheckForJniException(env)) {
-        LOG(DEBUG) << "Java exception while deleting file";
         return EFAULT;
     }
-    LOG(DEBUG) << "res = " << res;
     return res;
 }
 
 int isOpenAllowedInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_is_open_allowed,
                           const string& path, uid_t uid, bool for_write) {
-    LOG(DEBUG) << "Checking if UID = " << uid << " can open file " << path << " for "
-               << (for_write ? "write" : "read only");
     ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
     int res = env->CallIntMethod(media_provider_object, mid_is_open_allowed, j_path.get(), uid,
                                  for_write);
 
     if (CheckForJniException(env)) {
-        LOG(DEBUG) << "Java exception while checking permissions for file";
         return EFAULT;
     }
-    LOG(DEBUG) << "res = " << res;
     return res;
 }
 
 void scanFileInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_scan_file,
                       const string& path) {
-    LOG(DEBUG) << "Notifying MediaProvider that a file has been modified. path = " << path;
     ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
     env->CallVoidMethod(media_provider_object, mid_scan_file, j_path.get());
-    if (CheckForJniException(env)) {
-        LOG(DEBUG) << "Java exception while checking permissions for file";
-    }
-    LOG(DEBUG) << "MediaProvider has been notified";
+    CheckForJniException(env);
 }
 
-int isDirectoryOperationAllowedInternal(JNIEnv* env, jobject media_provider_object,
-                                        jmethodID mid_is_dir_op_allowed, const string& path,
-                                        uid_t uid) {
+int isMkdirOrRmdirAllowedInternal(JNIEnv* env, jobject media_provider_object,
+                                  jmethodID mid_is_mkdir_or_rmdir_allowed, const string& path,
+                                  uid_t uid, bool forCreate) {
     ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
-    int res = env->CallIntMethod(media_provider_object, mid_is_dir_op_allowed, j_path.get(), uid);
+    int res = env->CallIntMethod(media_provider_object, mid_is_mkdir_or_rmdir_allowed, j_path.get(),
+                                 uid, forCreate);
 
     if (CheckForJniException(env)) {
-        LOG(DEBUG) << "Java exception while checking permissions for creating/deleting/opening dir";
         return EFAULT;
     }
-    LOG(DEBUG) << "res = " << res;
+    return res;
+}
+
+int isOpendirAllowedInternal(JNIEnv* env, jobject media_provider_object,
+                             jmethodID mid_is_opendir_allowed, const string& path, uid_t uid) {
+    ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
+    int res = env->CallIntMethod(media_provider_object, mid_is_opendir_allowed, j_path.get(), uid);
+
+    if (CheckForJniException(env)) {
+        return EFAULT;
+    }
     return res;
 }
 
 std::vector<std::shared_ptr<DirectoryEntry>> getFilesInDirectoryInternal(
         JNIEnv* env, jobject media_provider_object, jmethodID mid_get_files_in_dir, uid_t uid,
         const string& path) {
-    LOG(DEBUG) << "Getting file names in path " << path << " for UID = " << uid;
     std::vector<std::shared_ptr<DirectoryEntry>> directory_entries;
     ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
 
@@ -169,7 +158,6 @@
                          media_provider_object, mid_get_files_in_dir, j_path.get(), uid)));
 
     if (CheckForJniException(env)) {
-        LOG(ERROR) << "Exception occurred while calling MediaProvider#getFilesInDirectoryForFuse";
         directory_entries.push_back(std::make_shared<DirectoryEntry>("", EFAULT));
         return directory_entries;
     }
@@ -208,16 +196,13 @@
 
 int renameInternal(JNIEnv* env, jobject media_provider_object, jmethodID mid_rename,
                    const string& old_path, const string& new_path, uid_t uid) {
-    LOG(DEBUG) << "Renaming " << old_path << " to " << new_path << ", uid: " << uid;
     ScopedLocalRef<jstring> j_old_path(env, env->NewStringUTF(old_path.c_str()));
     ScopedLocalRef<jstring> j_new_path(env, env->NewStringUTF(new_path.c_str()));
     int res = env->CallIntMethod(media_provider_object, mid_rename, j_old_path.get(),
                                  j_new_path.get(), uid);
     if (CheckForJniException(env)) {
-        LOG(DEBUG) << "Java exception occurred while renaming a file or directory";
         return EFAULT;
     }
-    LOG(DEBUG) << "res = " << res;
     return res;
 }
 }  // namespace
@@ -251,8 +236,8 @@
                                        /*is_static*/ false);
     mid_scan_file_ = CacheMethod(env, "scanFile", "(Ljava/lang/String;)V",
                                  /*is_static*/ false);
-    mid_is_dir_op_allowed_ = CacheMethod(env, "isDirectoryOperationAllowed",
-                                         "(Ljava/lang/String;I)I", /*is_static*/ false);
+    mid_is_mkdir_or_rmdir_allowed_ = CacheMethod(env, "isDirectoryCreationOrDeletionAllowed",
+                                                 "(Ljava/lang/String;IZ)I", /*is_static*/ false);
     mid_is_opendir_allowed_ = CacheMethod(env, "isOpendirAllowed", "(Ljava/lang/String;I)I",
                                           /*is_static*/ false);
     mid_get_files_in_dir_ =
@@ -278,7 +263,7 @@
     // because the flag value has already been changed. This ensures that the
     // termination task is the last task in the queue.
 
-    LOG(DEBUG) << "Posting task to terminate JNI thread";
+    LOG(INFO) << "Posting task to terminate JNI thread";
     // async task doesn't check jni_tasks_welcome_ - but we will wait for the thread to terminate
     // anyway
     PostAsyncTask([this](JNIEnv* env) {
@@ -371,9 +356,9 @@
     int res = EIO;  // Default value in case JNI thread was being terminated
 
     PostAndWaitForTask([this, &path, uid, &res](JNIEnv* env) {
-        LOG(DEBUG) << "Checking if UID = " << uid << " can create dir " << path;
-        res = isDirectoryOperationAllowedInternal(env, media_provider_object_,
-                                                  mid_is_dir_op_allowed_, path, uid);
+        res = isMkdirOrRmdirAllowedInternal(env, media_provider_object_,
+                                            mid_is_mkdir_or_rmdir_allowed_, path, uid,
+                                            /*forCreate*/ true);
     });
 
     return res;
@@ -387,9 +372,9 @@
     int res = EIO;  // Default value in case JNI thread was being terminated
 
     PostAndWaitForTask([this, &path, uid, &res](JNIEnv* env) {
-        LOG(DEBUG) << "Checking if UID = " << uid << " can delete dir " << path;
-        res = isDirectoryOperationAllowedInternal(env, media_provider_object_,
-                                                  mid_is_dir_op_allowed_, path, uid);
+        res = isMkdirOrRmdirAllowedInternal(env, media_provider_object_,
+                                            mid_is_mkdir_or_rmdir_allowed_, path, uid,
+                                            /*forCreate*/ false);
     });
     return res;
 }
@@ -428,9 +413,8 @@
     int res = EIO;  // Default value in case JNI thread was being terminated
 
     PostAndWaitForTask([this, &path, uid, &res](JNIEnv* env) {
-        LOG(DEBUG) << "Checking if UID = " << uid << " can open dir " << path;
-        res = isDirectoryOperationAllowedInternal(env, media_provider_object_,
-                                                  mid_is_opendir_allowed_, path, uid);
+        res = isOpendirAllowedInternal(env, media_provider_object_, mid_is_opendir_allowed_, path,
+                                       uid);
     });
 
     return res;
diff --git a/jni/MediaProviderWrapper.h b/jni/MediaProviderWrapper.h
index b3a8f68..64c27f6 100644
--- a/jni/MediaProviderWrapper.h
+++ b/jni/MediaProviderWrapper.h
@@ -164,7 +164,7 @@
     jmethodID mid_delete_file_;
     jmethodID mid_is_open_allowed_;
     jmethodID mid_scan_file_;
-    jmethodID mid_is_dir_op_allowed_;
+    jmethodID mid_is_mkdir_or_rmdir_allowed_;
     jmethodID mid_is_opendir_allowed_;
     jmethodID mid_get_files_in_dir_;
     jmethodID mid_rename_;
diff --git a/jni/com_android_providers_media_FuseDaemon.cpp b/jni/com_android_providers_media_FuseDaemon.cpp
index caf00b2..87861ca 100644
--- a/jni/com_android_providers_media_FuseDaemon.cpp
+++ b/jni/com_android_providers_media_FuseDaemon.cpp
@@ -73,6 +73,22 @@
     return JNI_FALSE;
 }
 
+void com_android_providers_media_FuseDaemon_invalidate_fuse_dentry_cache(JNIEnv* env, jobject self,
+                                                                         jlong java_daemon,
+                                                                         jstring java_path) {
+    fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
+    if (daemon) {
+        ScopedUtfChars utf_chars_path(env, java_path);
+        if (!utf_chars_path.c_str()) {
+            // TODO(b/145741152): Throw exception
+            return;
+        }
+
+        daemon->InvalidateFuseDentryCache(utf_chars_path.c_str());
+    }
+    // TODO(b/145741152): Throw exception
+}
+
 const JNINativeMethod methods[] = {
         {"native_new", "(Lcom/android/providers/media/MediaProvider;)J",
          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_new)},
@@ -81,8 +97,10 @@
         {"native_delete", "(J)V",
          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_delete)},
         {"native_should_open_with_fuse", "(JLjava/lang/String;ZI)Z",
-         reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_should_open_with_fuse)}};
-
+         reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_should_open_with_fuse)},
+        {"native_invalidate_fuse_dentry_cache", "(JLjava/lang/String;)V",
+         reinterpret_cast<void*>(
+                 com_android_providers_media_FuseDaemon_invalidate_fuse_dentry_cache)}};
 }  // namespace
 
 void register_android_providers_media_FuseDaemon(JNIEnv* env) {
diff --git a/jni/node-inl.h b/jni/node-inl.h
index 76d1ba9..e21ae54 100644
--- a/jni/node-inl.h
+++ b/jni/node-inl.h
@@ -160,6 +160,11 @@
         return name_;
     }
 
+    node* GetParent() const {
+        std::lock_guard<std::recursive_mutex> guard(*lock_);
+        return parent_;
+    }
+
     inline void AddHandle(handle* h) {
         std::lock_guard<std::recursive_mutex> guard(*lock_);
         handles_.emplace_back(std::unique_ptr<handle>(h));
diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java
index 4943d6b..e95bc83 100644
--- a/src/com/android/providers/media/DatabaseHelper.java
+++ b/src/com/android/providers/media/DatabaseHelper.java
@@ -1269,4 +1269,8 @@
                 "SELECT COUNT(_id) FROM files WHERE " + FileColumns.MIME_TYPE + " IS NOT NULL",
                 null);
     }
+
+    public boolean isExternal() {
+        return !mInternal;
+    }
 }
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index b152515..2dafd9d 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -252,6 +252,7 @@
     private static final String DIRECTORY_DOCUMENTS = "Documents";
     private static final String DIRECTORY_AUDIOBOOKS = "Audiobooks";
     private static final String DIRECTORY_ANDROID = "Android";
+
     private static final String DIRECTORY_MEDIA = "media";
 
     /**
@@ -441,12 +442,50 @@
         }
     };
 
+    private final void updateQuotaTypeForUri(@NonNull Uri uri, int mediaType) {
+        File file;
+        try {
+            file = queryForDataFile(uri, null);
+        } catch (FileNotFoundException e) {
+            // Ignore
+            return;
+        }
+        try {
+            switch (mediaType) {
+                case FileColumns.MEDIA_TYPE_AUDIO:
+                    mStorageManager.updateExternalStorageFileQuotaType(file,
+                            StorageManager.QUOTA_TYPE_MEDIA_AUDIO);
+                    break;
+                case FileColumns.MEDIA_TYPE_VIDEO:
+                    mStorageManager.updateExternalStorageFileQuotaType(file,
+                            StorageManager.QUOTA_TYPE_MEDIA_VIDEO);
+                    break;
+                case FileColumns.MEDIA_TYPE_IMAGE:
+                    mStorageManager.updateExternalStorageFileQuotaType(file,
+                            StorageManager.QUOTA_TYPE_MEDIA_IMAGE);
+                    break;
+                default:
+                    mStorageManager.updateExternalStorageFileQuotaType(file,
+                            StorageManager.QUOTA_TYPE_MEDIA_NONE);
+                    break;
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to update quota type for " + file.getPath(), e);
+        }
+    }
+
     private final OnFilesChangeListener mFilesListener = new OnFilesChangeListener() {
         @Override
         public void onInsert(@NonNull DatabaseHelper helper, @NonNull String volumeName, long id,
                 int mediaType, boolean isDownload) {
             acceptWithExpansion(helper::notifyChange, volumeName, id, mediaType, isDownload);
 
+            if (helper.isExternal()) {
+                // Update the quota type on the filesystem
+                Uri fileUri = MediaStore.Files.getContentUri(volumeName, id);
+                updateQuotaTypeForUri(fileUri, mediaType);
+            }
+
             // Tell our SAF provider so it knows when views are no longer empty
             MediaDocumentsProvider.onMediaStoreInsert(getContext(), volumeName, mediaType, id);
         }
@@ -461,8 +500,12 @@
             // When media type changes, notify both old and new collections and
             // invalidate any thumbnails
             if (newMediaType != oldMediaType) {
+                Uri fileUri = MediaStore.Files.getContentUri(volumeName, id);
+                if (helper.isExternal()) {
+                    updateQuotaTypeForUri(fileUri, newMediaType);
+                }
                 acceptWithExpansion(helper::notifyChange, volumeName, id, newMediaType, isDownload);
-                invalidateThumbnails(MediaStore.Files.getContentUri(volumeName, id));
+                invalidateThumbnails(fileUri);
             }
         }
 
@@ -533,20 +576,27 @@
         }
     }
 
-
-
     private static final String[] sDefaultFolderNames = {
-        Environment.DIRECTORY_MUSIC,
-        Environment.DIRECTORY_PODCASTS,
-        Environment.DIRECTORY_RINGTONES,
-        Environment.DIRECTORY_ALARMS,
-        Environment.DIRECTORY_NOTIFICATIONS,
-        Environment.DIRECTORY_PICTURES,
-        Environment.DIRECTORY_MOVIES,
-        Environment.DIRECTORY_DOWNLOADS,
-        Environment.DIRECTORY_DCIM,
+            Environment.DIRECTORY_MUSIC,
+            Environment.DIRECTORY_PODCASTS,
+            Environment.DIRECTORY_RINGTONES,
+            Environment.DIRECTORY_ALARMS,
+            Environment.DIRECTORY_NOTIFICATIONS,
+            Environment.DIRECTORY_PICTURES,
+            Environment.DIRECTORY_MOVIES,
+            Environment.DIRECTORY_DOWNLOADS,
+            Environment.DIRECTORY_DCIM,
     };
 
+    private static boolean isDefaultDirectoryName(@Nullable String dirName) {
+        for (String defaultDirName : sDefaultFolderNames) {
+            if (defaultDirName.equals(dirName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Ensure that default folders are created on mounted primary storage
      * devices. We only do this once per volume so we don't annoy the user if
@@ -557,7 +607,12 @@
             final File path = getVolumePath(volumeName);
             final StorageVolume vol = mStorageManager.getStorageVolume(path);
             final String key;
-            if (vol == null || vol.isPrimary()) {
+            if (vol == null) {
+                Log.w(TAG, "Failed to ensure default folders for " + volumeName);
+                return;
+            }
+
+            if (vol.isPrimary()) {
                 key = "created_default_folders";
             } else {
                 key = "created_default_folders_" + vol.getMediaStoreVolumeName();
@@ -4775,6 +4830,14 @@
         return new File(filePath);
     }
 
+    private FuseDaemon getFuseDaemonForFile(File file) {
+        StorageVolume volume = mStorageManager.getStorageVolume(file);
+        if (volume == null) {
+            return null;
+        }
+        return ExternalStorageServiceImpl.getFuseDaemon(volume.getId());
+    }
+
     /**
      * Replacement for {@link #openFileHelper(Uri, String)} which enforces any
      * permissions applicable to the path before returning.
@@ -4882,12 +4945,7 @@
                             redactionInfo.freeOffsets);
                 }
             } else {
-                FuseDaemon daemon = null;
-
-                StorageVolume volume = mStorageManager.getStorageVolume(file);
-                if (volume != null) {
-                    daemon = ExternalStorageServiceImpl.getFuseDaemon(volume.getId());
-                }
+                FuseDaemon daemon = getFuseDaemonForFile(file);
                 ParcelFileDescriptor lowerFsFd = ParcelFileDescriptor.open(file, modeBits);
                 boolean forRead = (modeBits & ParcelFileDescriptor.MODE_READ_ONLY) != 0;
                 boolean shouldOpenWithFuse = daemon != null
@@ -5582,6 +5640,7 @@
      *
      * @param path File path of the directory that the app wants to create/delete
      * @param uid UID of the app that wants to create/delete the directory
+     * @param forCreate denotes whether the operation is directory creation or deletion
      * @return 0 if the operation is allowed, or the following {@code errno} values:
      * <ul>
      * <li>{@link OsConstants#EACCES} if the app tries to create/delete a dir in another app's
@@ -5593,7 +5652,8 @@
      * Called from JNI in jni/MediaProviderWrapper.cpp
      */
     @Keep
-    public int isDirectoryOperationAllowedForFuse(@NonNull String path, int uid) {
+    public int isDirectoryCreationOrDeletionAllowedForFuse(
+            @NonNull String path, int uid, boolean forCreate) {
         final LocalCallingIdentity token =
                 clearLocalCallingIdentity(LocalCallingIdentity.fromExternal(getContext(), uid));
         try {
@@ -5621,8 +5681,17 @@
             }
 
             final String[] relativePath = sanitizePath(extractRelativePath(path));
-            if (relativePath.length == 1 && TextUtils.isEmpty(relativePath[0])) {
-                Log.e(TAG, "Creating or deleting a top level directory is not allowed!");
+            final boolean isTopLevelDir =
+                    relativePath.length == 1 && TextUtils.isEmpty(relativePath[0]);
+            if (isTopLevelDir) {
+                // We allow creating the default top level directories only, all other oprations on
+                // top level directories are not allowed.
+                if (forCreate && isDefaultDirectoryName(extractDisplayName(path))) {
+                    return 0;
+                }
+                Log.e(TAG,
+                        "Creating a non-default top level directory or deleting an existing"
+                                + " one is not allowed!");
                 return OsConstants.EPERM;
             }
             return 0;
diff --git a/src/com/android/providers/media/fuse/FuseDaemon.java b/src/com/android/providers/media/fuse/FuseDaemon.java
index 19ebc03..3ce2160 100644
--- a/src/com/android/providers/media/fuse/FuseDaemon.java
+++ b/src/com/android/providers/media/fuse/FuseDaemon.java
@@ -99,9 +99,17 @@
         return native_should_open_with_fuse(mPtr, path, readLock, fd);
     }
 
+    /**
+     * Invalidates FUSE VFS dentry cache for {@code path}
+     */
+    public void invalidateFuseDentryCache(String path) {
+        native_invalidate_fuse_dentry_cache(mPtr, path);
+    }
+
     private native long native_new(MediaProvider mediaProvider);
     private native void native_start(long daemon, int deviceFd, String path);
     private native void native_delete(long daemon);
     private native boolean native_should_open_with_fuse(long daemon, String path, boolean readLock,
             int fd);
+    private native void native_invalidate_fuse_dentry_cache(long daemon, String path);
 }
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 eea937d..6af8a3a 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
@@ -225,6 +225,11 @@
     }
 
     @Test
+    public void testCanCreateDefaultDirectory() throws Exception {
+        runDeviceTest("testCanCreateDefaultDirectory");
+    }
+
+    @Test
     public void testManageExternalStorageQueryOtherAppsFile() throws Exception {
         runDeviceTest("testManageExternalStorageQueryOtherAppsFile");
     }
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 1201e03..8f47edd 100644
--- a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
+++ b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
@@ -107,6 +107,7 @@
     static final File MOVIES_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_MOVIES);
     static final File DOWNLOAD_DIR = new File(EXTERNAL_STORAGE_DIR,
             Environment.DIRECTORY_DOWNLOADS);
+    static final File PODCASTS_DIR = new File(EXTERNAL_STORAGE_DIR, Environment.DIRECTORY_PODCASTS);
     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";
@@ -1381,6 +1382,20 @@
     }
 
     @Test
+    public void testCanCreateDefaultDirectory() throws Exception {
+        try {
+            if (PODCASTS_DIR.exists()) {
+                // Apps can't delete top level directories, not even default directories, so we let
+                // shell do the deed for us.
+                executeShellCommand("rm -r " + PODCASTS_DIR);
+            }
+            assertThat(PODCASTS_DIR.mkdir()).isTrue();
+        } finally {
+            executeShellCommand("mkdir " + PODCASTS_DIR);
+        }
+    }
+
+    @Test
     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);