Merge "Make single arg constructor explicit" into rvc-dev
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index 5660c9b..4caad75 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -378,6 +378,25 @@
     return std::regex_match(path, PATTERN_OWNED_PATH);
 }
 
+static void invalidate_case_insensitive_dentry_matches(struct fuse* fuse, node* parent,
+                                                       const string& name) {
+    vector<string> children = parent->MatchChildrenCaseInsensitive(name);
+    if (children.empty() || (children.size() == 1 && children[0] == name)) {
+        return;
+    }
+
+    fuse_ino_t parent_ino = fuse->ToInode(parent);
+    std::thread t([=]() {
+        for (const string& child_name : children) {
+            if (fuse_lowlevel_notify_inval_entry(fuse->se, parent_ino, child_name.c_str(),
+                                                 child_name.size())) {
+                LOG(ERROR) << "Failed to invalidate dentry " << child_name;
+            }
+        }
+    });
+    t.detach();
+}
+
 static node* make_node_entry(fuse_req_t req, node* parent, const string& name, const string& path,
                              struct fuse_entry_param* e, int* error_code) {
     struct fuse* fuse = get_fuse(req);
@@ -396,6 +415,9 @@
     }
 
     TRACE_NODE(node);
+
+    invalidate_case_insensitive_dentry_matches(fuse, parent, name);
+
     // This FS is not being exported via NFS so just a fixed generation number
     // for now. If we do need this, we need to increment the generation ID each
     // time the fuse daemon restarts because that's what it takes for us to
@@ -1610,7 +1632,7 @@
     }
     fuse_default.se = se;
     fuse_default.active = &active;
-    se->fd = fd;
+    se->fd = fd.release();  // libfuse owns the FD now
     se->mountpoint = strdup(path.c_str());
 
     // Single thread. Useful for debugging
diff --git a/jni/node-inl.h b/jni/node-inl.h
index e5ede88..c238fc8 100644
--- a/jni/node-inl.h
+++ b/jni/node-inl.h
@@ -221,6 +221,22 @@
         return parent_;
     }
 
+    std::vector<std::string> MatchChildrenCaseInsensitive(const std::string& name) const {
+        std::lock_guard<std::recursive_mutex> guard(*lock_);
+
+        const char* name_char = name.c_str();
+        std::vector<std::string> matches;
+
+        for (node* child : children_) {
+            std::string child_name = child->GetName();
+            if (!strcasecmp(name_char, child_name.c_str())) {
+                matches.push_back(child_name);
+            }
+        }
+
+        return matches;
+    }
+
     inline void AddHandle(handle* h) {
         std::lock_guard<std::recursive_mutex> guard(*lock_);
         handles_.emplace_back(std::unique_ptr<handle>(h));
diff --git a/jni/node_test.cpp b/jni/node_test.cpp
index 027181d..901704d 100644
--- a/jni/node_test.cpp
+++ b/jni/node_test.cpp
@@ -228,3 +228,17 @@
             new handle(-1, new mediaprovider::fuse::RedactionInfo, true /* cached */));
     EXPECT_DEATH(node->DestroyHandle(h2.get()), "");
 }
+
+TEST_F(NodeTest, CaseInsensitive) {
+    unique_node_ptr parent = CreateNode(nullptr, "/path");
+    unique_node_ptr lower_child = CreateNode(parent.get(), "child");
+    unique_node_ptr upper_child = CreateNode(parent.get(), "CHILD");
+    unique_node_ptr mixed_child = CreateNode(parent.get(), "cHiLd");
+
+    std::vector<std::string> children = parent->MatchChildrenCaseInsensitive("ChIld");
+
+    ASSERT_EQ(3, children.size());
+    ASSERT_EQ("child", children[0]);
+    ASSERT_EQ("CHILD", children[1]);
+    ASSERT_EQ("cHiLd", children[2]);
+}
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 74d70be..5c4d315 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -23,7 +23,7 @@
     <string name="unknown" msgid="2059049215682829375">"غير معروف"</string>
     <string name="root_images" msgid="5861633549189045666">"الصور"</string>
     <string name="root_videos" msgid="8792703517064649453">"الفيديوهات"</string>
-    <string name="root_audio" msgid="3505830755201326018">"الصوت"</string>
+    <string name="root_audio" msgid="3505830755201326018">"صوتيات"</string>
     <string name="root_documents" msgid="3829103301363849237">"المستندات"</string>
     <string name="permission_required" msgid="1460820436132943754">"مطلوب الحصول على إذن لتعديل هذا العنصر أو حذفه."</string>
     <string name="permission_required_action" msgid="706370952366113539">"متابعة"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 4279f89..12ec655 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -21,7 +21,7 @@
     <string name="app_label" msgid="9035307001052716210">"मीडिया मेमोरी"</string>
     <string name="artist_label" msgid="8105600993099120273">"कलाकार"</string>
     <string name="unknown" msgid="2059049215682829375">"अज्ञात"</string>
-    <string name="root_images" msgid="5861633549189045666">"चित्र"</string>
+    <string name="root_images" msgid="5861633549189045666">"इमेज"</string>
     <string name="root_videos" msgid="8792703517064649453">"वीडियो"</string>
     <string name="root_audio" msgid="3505830755201326018">"ऑडियो"</string>
     <string name="root_documents" msgid="3829103301363849237">"दस्तावेज़"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 1efefcd..2eed59d 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -21,7 +21,7 @@
     <string name="app_label" msgid="9035307001052716210">"ਮੀਡੀਆ ਸਟੋਰੇਜ"</string>
     <string name="artist_label" msgid="8105600993099120273">"ਕਲਾਕਾਰ"</string>
     <string name="unknown" msgid="2059049215682829375">"ਅਗਿਆਤ"</string>
-    <string name="root_images" msgid="5861633549189045666">"ਚਿਤਰ"</string>
+    <string name="root_images" msgid="5861633549189045666">"ਚਿੱਤਰ"</string>
     <string name="root_videos" msgid="8792703517064649453">"ਵੀਡੀਓ"</string>
     <string name="root_audio" msgid="3505830755201326018">" ਆਡੀਓ"</string>
     <string name="root_documents" msgid="3829103301363849237">"ਦਸਤਾਵੇਜ਼"</string>
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index 90a9ed1..5bd8c80 100644
--- a/src/com/android/providers/media/LocalCallingIdentity.java
+++ b/src/com/android/providers/media/LocalCallingIdentity.java
@@ -33,6 +33,7 @@
 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteImages;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteStorage;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteVideo;
+import static com.android.providers.media.util.PermissionUtils.generateAppOpMessage;
 
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
@@ -58,7 +59,9 @@
     public final int pid;
     public final int uid;
     public final String packageNameUnchecked;
+    // Info used for logging permission checks
     public @Nullable String attributionTag;
+    private @Nullable String opDescription;
 
     private LocalCallingIdentity(Context context, int pid, int uid, String packageNameUnchecked,
             @Nullable String attributionTag) {
@@ -67,6 +70,7 @@
         this.uid = uid;
         this.packageNameUnchecked = packageNameUnchecked;
         this.attributionTag = attributionTag;
+        this.opDescription = null;
     }
 
     /**
@@ -212,7 +216,7 @@
 
     public boolean hasPermission(int permission) {
         if ((hasPermissionResolved & permission) == 0) {
-            if (hasPermissionInternal(permission)) {
+            if (hasPermissionInternal(permission, opDescription)) {
                 hasPermission |= permission;
             }
             hasPermissionResolved |= permission;
@@ -220,7 +224,7 @@
         return (hasPermission & permission) != 0;
     }
 
-    private boolean hasPermissionInternal(int permission) {
+    private boolean hasPermissionInternal(int permission, @Nullable String description) {
         // While we're here, enforce any broad user-level restrictions
         if ((uid == Process.SHELL_UID) && context.getSystemService(UserManager.class)
                 .hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
@@ -242,19 +246,26 @@
             case PERMISSION_IS_REDACTION_NEEDED:
                 return isRedactionNeededInternal();
             case PERMISSION_READ_AUDIO:
-                return checkPermissionReadAudio(context, pid, uid, getPackageName());
+                return checkPermissionReadAudio(context, pid, uid, getPackageName(), attributionTag,
+                        generateAppOpMessage(packageName, description));
             case PERMISSION_READ_VIDEO:
-                return checkPermissionReadVideo(context, pid, uid, getPackageName());
+                return checkPermissionReadVideo(context, pid, uid, getPackageName(), attributionTag,
+                        generateAppOpMessage(packageName, description));
             case PERMISSION_READ_IMAGES:
-                return checkPermissionReadImages(context, pid, uid, getPackageName());
+                return checkPermissionReadImages(context, pid, uid, getPackageName(),
+                        attributionTag, generateAppOpMessage(packageName, description));
             case PERMISSION_WRITE_AUDIO:
-                return checkPermissionWriteAudio(context, pid, uid, getPackageName());
+                return checkPermissionWriteAudio(context, pid, uid, getPackageName(),
+                        attributionTag, generateAppOpMessage(packageName, description));
             case PERMISSION_WRITE_VIDEO:
-                return checkPermissionWriteVideo(context, pid, uid, getPackageName());
+                return checkPermissionWriteVideo(context, pid, uid, getPackageName(),
+                        attributionTag, generateAppOpMessage(packageName, description));
             case PERMISSION_WRITE_IMAGES:
-                return checkPermissionWriteImages(context, pid, uid, getPackageName());
+                return checkPermissionWriteImages(context, pid, uid, getPackageName(),
+                        attributionTag, generateAppOpMessage(packageName, description));
             case PERMISSION_MANAGE_EXTERNAL_STORAGE:
-                return checkPermissionManageExternalStorage(context, pid, uid, getPackageName());
+                return checkPermissionManageExternalStorage(context, pid, uid, getPackageName(),
+                        attributionTag, generateAppOpMessage(packageName, description));
             default:
                 return false;
         }
@@ -297,13 +308,15 @@
     }
 
     private boolean isLegacyWriteInternal() {
-        return hasPermission(PERMISSION_IS_LEGACY_GRANTED) &&
-                checkPermissionWriteStorage(context, pid, uid, getPackageName());
+        return hasPermission(PERMISSION_IS_LEGACY_GRANTED)
+                && checkPermissionWriteStorage(context, pid, uid, getPackageName(), attributionTag,
+                        /*opMessage*/ null);
     }
 
     private boolean isLegacyReadInternal() {
-        return hasPermission(PERMISSION_IS_LEGACY_GRANTED) &&
-                checkPermissionReadStorage(context, pid, uid, getPackageName());
+        return hasPermission(PERMISSION_IS_LEGACY_GRANTED)
+                && checkPermissionReadStorage(context, pid, uid, getPackageName(), attributionTag,
+                        /*opMessage*/ null);
     }
 
     /** System internals or callers holding permission have no redaction */
@@ -358,4 +371,8 @@
     public long getDeletedRowId(@NonNull String path) {
         return rowIdOfDeletedPaths.getOrDefault(path, UNKNOWN_ROW_ID);
     }
+
+    public void setOpDescription(@Nullable String opDescription) {
+        this.opDescription = opDescription;
+    }
 }
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 952f895..762dbf4 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -66,6 +66,7 @@
 import static com.android.providers.media.util.Logging.LOGV;
 import static com.android.providers.media.util.Logging.TAG;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionManageExternalStorage;
+import static com.android.providers.media.util.PermissionUtils.generateAppOpMessage;
 
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.OnOpActiveChangedListener;
@@ -120,7 +121,6 @@
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.OnCloseListener;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -407,13 +407,19 @@
     private OnOpChangedListener mModeListener =
             (op, packageName) -> invalidateLocalCallingIdentityCache(packageName, "op " + op);
 
-    private LocalCallingIdentity getCachedCallingIdentityForFuse(int uid) {
+    /**
+     * Retrieves a cached calling identity or creates a new one. Also, always sets the app-op
+     * description for the calling identity.
+     */
+    private LocalCallingIdentity getCachedCallingIdentityForFuse(
+            int uid, @Nullable String opDescription) {
         synchronized (mCachedCallingIdentityForFuse) {
             LocalCallingIdentity ident = mCachedCallingIdentityForFuse.get(uid);
             if (ident == null) {
                ident = LocalCallingIdentity.fromExternal(getContext(), uid);
                mCachedCallingIdentityForFuse.put(uid, ident);
             }
+            ident.setOpDescription(opDescription);
             return ident;
         }
     }
@@ -1054,7 +1060,9 @@
      * to clear other apps' cache directories.
      */
     static boolean hasPermissionToClearCaches(Context context, ApplicationInfo ai) {
-        return checkPermissionManageExternalStorage(context, /*pid*/-1, ai.uid, ai.packageName);
+        final String opMessage = generateAppOpMessage(ai.packageName, "clear app cache");
+        return checkPermissionManageExternalStorage(context, /*pid*/ -1, ai.uid, ai.packageName,
+                /*attributionTag*/ null, opMessage);
     }
 
     /**
@@ -1272,7 +1280,7 @@
     @Keep
     public String[] getFilesInDirectoryForFuse(String path, int uid) {
         final LocalCallingIdentity token =
-                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid, "readdir " + path));
 
         try {
             if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -1759,8 +1767,8 @@
     @Keep
     public int renameForFuse(String oldPath, String newPath, int uid) {
         final String errorMessage = "Rename " + oldPath + " to " + newPath + " failed. ";
-        final LocalCallingIdentity token =
-                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+        final LocalCallingIdentity token = clearLocalCallingIdentity(
+                getCachedCallingIdentityForFuse(uid, "rename " + oldPath + " to " + newPath));
 
         try {
             if (isPrivatePackagePathNotOwnedByCaller(oldPath)
@@ -1843,7 +1851,8 @@
     public int checkUriPermission(@NonNull Uri uri, int uid,
             /* @Intent.AccessUriMode */ int modeFlags) {
         final LocalCallingIdentity token =
-                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid,
+                        /*opDescription*/ null));
 
         try {
             final boolean allowHidden = isCallingPackageAllowedHidden();
@@ -2694,7 +2703,12 @@
 
         if (mimeType != null) {
             values.put(FileColumns.MIME_TYPE, mimeType);
-            values.put(FileColumns.MEDIA_TYPE, MimeUtils.resolveMediaType(mimeType));
+            if (isCallingPackageSystem() && values.containsKey(FileColumns.MEDIA_TYPE)) {
+                // Leave FileColumns.MEDIA_TYPE untouched if the caller is ModernMediaScanner and
+                // FileColumns.MEDIA_TYPE is already populated.
+            } else{
+                values.put(FileColumns.MEDIA_TYPE, MimeUtils.resolveMediaType(mimeType));
+            }
         } else {
             values.put(FileColumns.MEDIA_TYPE, mediaType);
         }
@@ -5797,8 +5811,8 @@
             return getRedactionRanges(file).redactionRanges;
         }
 
-        final LocalCallingIdentity token =
-                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+        final LocalCallingIdentity token = clearLocalCallingIdentity(
+                getCachedCallingIdentityForFuse(uid, "read metadata from " + path));
 
         long[] res = new long[0];
         try {
@@ -5918,9 +5932,8 @@
      */
     @Keep
     public int isOpenAllowedForFuse(String path, int uid, boolean forWrite) {
-        final LocalCallingIdentity token =
-                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
-
+        final LocalCallingIdentity token = clearLocalCallingIdentity(
+                getCachedCallingIdentityForFuse(uid, (forWrite ? "write " : "read ") + path));
 
         try {
             if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -6111,7 +6124,7 @@
     @Keep
     public int insertFileIfNecessaryForFuse(@NonNull String path, int uid) {
         final LocalCallingIdentity token =
-                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid, "create " + path));
 
         try {
             if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -6181,7 +6194,7 @@
     @Keep
     public int deleteFileForFuse(@NonNull String path, int uid) throws IOException {
         final LocalCallingIdentity token =
-                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid, "delete " + path));
 
         try {
             if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -6248,8 +6261,8 @@
     @Keep
     public int isDirectoryCreationOrDeletionAllowedForFuse(
             @NonNull String path, int uid, boolean forCreate) {
-        final LocalCallingIdentity token =
-                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+        final LocalCallingIdentity token = clearLocalCallingIdentity(
+                getCachedCallingIdentityForFuse(uid, (forCreate ? "mkdir " : "rmdir ") + path));
 
         try {
             // App dirs are not indexed, so we don't create an entry for the file.
@@ -6301,8 +6314,8 @@
      */
     @Keep
     public int isOpendirAllowedForFuse(@NonNull String path, int uid) {
-        final LocalCallingIdentity token =
-                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+        final LocalCallingIdentity token = clearLocalCallingIdentity(
+                getCachedCallingIdentityForFuse(uid, "directory access " + path));
 
         try {
             if (isPrivatePackagePathNotOwnedByCaller(path)) {
@@ -6328,7 +6341,8 @@
     @Keep
     public boolean isUidForPackageForFuse(@NonNull String packageName, int uid) {
         final LocalCallingIdentity token =
-                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+                clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid,
+                        /*opDescription*/ null));
         try {
             return isCallingIdentitySharedPackageName(packageName);
         } finally {
diff --git a/src/com/android/providers/media/util/PermissionUtils.java b/src/com/android/providers/media/util/PermissionUtils.java
index 387ced0..e09feca 100644
--- a/src/com/android/providers/media/util/PermissionUtils.java
+++ b/src/com/android/providers/media/util/PermissionUtils.java
@@ -17,8 +17,6 @@
 package com.android.providers.media.util;
 
 import static android.Manifest.permission.BACKUP;
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
-import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
 import static android.app.AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE;
@@ -33,6 +31,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.provider.MediaStore;
@@ -44,8 +43,8 @@
 
     private static volatile int sLegacyMediaProviderUid = -1;
 
-    public static boolean checkPermissionSystem(Context context,
-            int pid, int uid, String packageName) {
+    public static boolean checkPermissionSystem(
+            @NonNull Context context, int pid, int uid, String packageName) {
         // Apps sharing legacy MediaProvider's uid like DownloadProvider and MTP are treated as
         // system.
         return uid == android.os.Process.SYSTEM_UID || uid == android.os.Process.myUid()
@@ -53,87 +52,124 @@
                 || isLegacyMediaProvider(context, uid);
     }
 
-    public static boolean checkPermissionBackup(Context context, int pid, int uid) {
+    public static boolean checkPermissionBackup(@NonNull Context context, int pid, int uid) {
         return context.checkPermission(BACKUP, pid, uid) == PERMISSION_GRANTED;
     }
 
-    public static boolean checkPermissionManageExternalStorage(Context context, int pid, int uid,
-            String packageName) {
-        return hasAppOpPermission(context, pid, uid, packageName, OPSTR_MANAGE_EXTERNAL_STORAGE);
+    public static boolean checkPermissionManageExternalStorage(@NonNull Context context, int pid,
+            int uid, @NonNull String packageName, @Nullable String attributionTag,
+            @Nullable String opMessage) {
+        return noteAppOpPermission(context, pid, uid, packageName, OPSTR_MANAGE_EXTERNAL_STORAGE,
+                attributionTag, opMessage);
     }
 
-    public static boolean checkPermissionWriteStorage(Context context,
-            int pid, int uid, String packageName) {
-        return checkPermissionAndAppOp(context, pid,
-                uid, packageName, WRITE_EXTERNAL_STORAGE, OPSTR_WRITE_EXTERNAL_STORAGE);
+    public static boolean checkPermissionWriteStorage(@NonNull Context context, int pid, int uid,
+            @NonNull String packageName, @Nullable String attributionTag,
+            @Nullable String opMessage) {
+        return noteAppOpPermission(context, pid, uid, packageName, OPSTR_WRITE_EXTERNAL_STORAGE,
+                attributionTag, opMessage);
     }
 
-    public static boolean checkPermissionReadStorage(Context context,
-            int pid, int uid, String packageName) {
-        return checkPermissionAndAppOp(context, pid,
-                uid, packageName, READ_EXTERNAL_STORAGE, OPSTR_READ_EXTERNAL_STORAGE);
+    public static boolean checkPermissionReadStorage(@NonNull Context context, int pid, int uid,
+            @NonNull String packageName, @Nullable String attributionTag,
+            @Nullable String opMessage) {
+        return noteAppOpPermission(context, pid, uid, packageName, OPSTR_READ_EXTERNAL_STORAGE,
+                attributionTag, opMessage);
     }
 
-    public static boolean checkIsLegacyStorageGranted(Context context, int uid,
-            String packageName) {
+    public static boolean checkIsLegacyStorageGranted(
+            @NonNull Context context, int uid, String packageName) {
         return context.getSystemService(AppOpsManager.class)
                 .unsafeCheckOp(OPSTR_LEGACY_STORAGE, uid, packageName) == MODE_ALLOWED;
     }
 
-    public static boolean checkPermissionReadAudio(Context context,
-            int pid, int uid, String packageName) {
-        if (!checkPermissionAndAppOp(context, pid, uid, packageName,
-                READ_EXTERNAL_STORAGE, OPSTR_READ_EXTERNAL_STORAGE)) return false;
-        return noteAppOpAllowingLegacy(context, pid, uid, packageName,
-                OPSTR_READ_MEDIA_AUDIO);
-    }
-
-    public static boolean checkPermissionWriteAudio(Context context,
-            int pid, int uid, String packageName) {
-        if (!checkPermissionAndAppOpAllowingNonLegacy(context, pid, uid, packageName,
-                WRITE_EXTERNAL_STORAGE, OPSTR_WRITE_EXTERNAL_STORAGE)) return false;
-        return noteAppOpAllowingLegacy(context, pid, uid, packageName,
-                OPSTR_WRITE_MEDIA_AUDIO);
-    }
-
-    public static boolean checkPermissionReadVideo(Context context,
-            int pid, int uid, String packageName) {
-        if (!checkPermissionAndAppOp(context, pid, uid, packageName,
-                READ_EXTERNAL_STORAGE, OPSTR_READ_EXTERNAL_STORAGE)) return false;
-        return noteAppOpAllowingLegacy(context, pid, uid, packageName,
-                OPSTR_READ_MEDIA_VIDEO);
-    }
-
-    public static boolean checkPermissionWriteVideo(Context context,
-            int pid, int uid, String packageName) {
-        if (!checkPermissionAndAppOpAllowingNonLegacy(context, pid, uid, packageName,
-                WRITE_EXTERNAL_STORAGE, OPSTR_WRITE_EXTERNAL_STORAGE)) return false;
-        return noteAppOpAllowingLegacy(context, pid, uid, packageName,
-                OPSTR_WRITE_MEDIA_VIDEO);
-    }
-
-    public static boolean checkPermissionReadImages(Context context,
-            int pid, int uid, String packageName) {
-        if (!checkPermissionAndAppOp(context, pid, uid, packageName,
-                READ_EXTERNAL_STORAGE, OPSTR_READ_EXTERNAL_STORAGE)) return false;
-        return noteAppOpAllowingLegacy(context, pid, uid, packageName,
-                OPSTR_READ_MEDIA_IMAGES);
-    }
-
-    public static boolean checkPermissionWriteImages(Context context,
-            int pid, int uid, String packageName) {
-        if (!checkPermissionAndAppOpAllowingNonLegacy(context, pid, uid, packageName,
-                WRITE_EXTERNAL_STORAGE, OPSTR_WRITE_EXTERNAL_STORAGE)) return false;
-        return noteAppOpAllowingLegacy(context, pid, uid, packageName,
-                OPSTR_WRITE_MEDIA_IMAGES);
-    }
-
-    private static boolean checkPermissionAndAppOp(Context context,
-            int pid, int uid, String packageName, String permission, String op) {
-        if (context.checkPermission(permission, pid, uid) != PERMISSION_GRANTED) {
+    public static boolean checkPermissionReadAudio(@NonNull Context context, int pid, int uid,
+            @NonNull String packageName, @Nullable String attributionTag,
+            @Nullable String opMessage) {
+        if (!checkPermissionAppOp(context, pid, uid, packageName, OPSTR_READ_EXTERNAL_STORAGE)) {
             return false;
         }
+        return noteAppOpAllowingLegacy(
+                context, pid, uid, packageName, OPSTR_READ_MEDIA_AUDIO, attributionTag, opMessage);
+    }
 
+    public static boolean checkPermissionWriteAudio(@NonNull Context context, int pid, int uid,
+            @NonNull String packageName, @Nullable String attributionTag,
+            @Nullable String opMessage) {
+        if (!checkPermissionAppOpAllowingNonLegacy(
+                    context, pid, uid, packageName, OPSTR_WRITE_EXTERNAL_STORAGE)) {
+            return false;
+        }
+        return noteAppOpAllowingLegacy(
+                context, pid, uid, packageName, OPSTR_WRITE_MEDIA_AUDIO, attributionTag, opMessage);
+    }
+
+    public static boolean checkPermissionReadVideo(@NonNull Context context, int pid, int uid,
+            @NonNull String packageName, @Nullable String attributionTag,
+            @Nullable String opMessage) {
+        if (!checkPermissionAppOp(context, pid, uid, packageName, OPSTR_READ_EXTERNAL_STORAGE)) {
+            return false;
+        }
+        return noteAppOpAllowingLegacy(
+                context, pid, uid, packageName, OPSTR_READ_MEDIA_VIDEO, attributionTag, opMessage);
+    }
+
+    public static boolean checkPermissionWriteVideo(@NonNull Context context, int pid, int uid,
+            @NonNull String packageName, @Nullable String attributionTag,
+            @Nullable String opMessage) {
+        if (!checkPermissionAppOpAllowingNonLegacy(
+                    context, pid, uid, packageName, OPSTR_WRITE_EXTERNAL_STORAGE)) {
+            return false;
+        }
+        return noteAppOpAllowingLegacy(
+                context, pid, uid, packageName, OPSTR_WRITE_MEDIA_VIDEO, attributionTag, opMessage);
+    }
+
+    public static boolean checkPermissionReadImages(@NonNull Context context, int pid, int uid,
+            @NonNull String packageName, @Nullable String attributionTag,
+            @Nullable String opMessage) {
+        if (!checkPermissionAppOp(context, pid, uid, packageName, OPSTR_READ_EXTERNAL_STORAGE)) {
+            return false;
+        }
+        return noteAppOpAllowingLegacy(
+                context, pid, uid, packageName, OPSTR_READ_MEDIA_IMAGES, attributionTag, opMessage);
+    }
+
+    public static boolean checkPermissionWriteImages(@NonNull Context context, int pid, int uid,
+            @NonNull String packageName, @Nullable String attributionTag,
+            @Nullable String opMessage) {
+        if (!checkPermissionAppOpAllowingNonLegacy(
+                    context, pid, uid, packageName, OPSTR_WRITE_EXTERNAL_STORAGE)) {
+            return false;
+        }
+        return noteAppOpAllowingLegacy(context, pid, uid, packageName, OPSTR_WRITE_MEDIA_IMAGES,
+                attributionTag, opMessage);
+    }
+
+    /**
+     * Generates a message to be used with the different {@link AppOpsManager#noteOp} variations.
+     * If the supplied description is {@code null}, the returned message will be {@code null}.
+     */
+    public static String generateAppOpMessage(
+            @NonNull String packageName, @Nullable String description) {
+        if (description == null) {
+            return null;
+        }
+        return "Package: " + packageName + ". Description: " + description + ".";
+    }
+
+    /**
+     * Checks the permission associated with the given app-op, if it's not granted, returns false.
+     * Else, checks the app-op and returns true iff it's {@link AppOpsManager#MODE_ALLOWED}.
+     * The permission is retrieved from {@link AppOpsManager#opToPermission(String)}.
+     */
+    private static boolean checkPermissionAppOp(@NonNull Context context, int pid, int uid,
+            @NonNull String packageName, @NonNull String op) {
+        final String permission = AppOpsManager.opToPermission(op);
+        if (permission != null
+                && context.checkPermission(permission, pid, uid) != PERMISSION_GRANTED) {
+            return false;
+        }
         final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
         try {
             appOps.checkPackage(uid, packageName);
@@ -155,50 +191,57 @@
     }
 
     /**
-     * Checks if the given package has the given {@code permission} and {@code op}, but allows it
-     * to bypass the permission and app-op check if it's NOT a legacy app, i.e. doesn't hold
-     * {@link AppOpsManager#OPSTR_LEGACY_STORAGE}. This is useful for deprecated permissions and/or
-     * app-ops, like {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
-     * @see #checkPermissionAndAppOp
+     * Similar to {@link #checkPermissionAppOp(Context, int, int, String, String)}, but also returns
+     * true for non-legacy apps.
+     * @see #checkPermissionAppOp
      */
-    private static boolean checkPermissionAndAppOpAllowingNonLegacy(Context context,
-            int pid, int uid, String packageName, String permission, String op) {
+    private static boolean checkPermissionAppOpAllowingNonLegacy(@NonNull Context context, int pid,
+            int uid, @NonNull String packageName, @NonNull String op) {
         final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
-        try {
-            appOps.checkPackage(uid, packageName);
-        } catch (SecurityException e) {
-            return false;
-        }
+
         // Allowing non legacy apps to bypass this check
         if (appOps.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE, uid,
                 packageName) != AppOpsManager.MODE_ALLOWED) return true;
 
         // Seems like it's a legacy app, so it has to pass the permission and app-op check
-        return checkPermissionAndAppOp(context, pid, uid, packageName, permission, op);
+        return checkPermissionAppOp(context, pid, uid, packageName, op);
     }
 
     /**
-     * Checks if calling app is allowed the app-op. If its app-op mode is
+     * Notes app-op for the callings package. If its app-op mode is
      * {@link AppOpsManager#MODE_DEFAULT} then it falls back to checking the appropriate permission
      * for the app-op. The permission is retrieved from
      * {@link AppOpsManager#opToPermission(String)}.
      */
-    private static boolean hasAppOpPermission(@NonNull Context context, int pid, int uid,
-            @NonNull String packageName, @NonNull String op) {
+    private static boolean noteAppOpPermission(@NonNull Context context, int pid, int uid,
+            @NonNull String packageName, @NonNull String op, @Nullable String attributionTag,
+            @Nullable String opMessage) {
         final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
-        final int mode = appOps.noteOpNoThrow(op, uid, packageName, null, null);
-        if (mode == AppOpsManager.MODE_DEFAULT) {
-            final String permission = AppOpsManager.opToPermission(op);
-            return permission != null
-                    && context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED;
+        final int mode = appOps.noteOpNoThrow(op, uid, packageName, attributionTag, opMessage);
+        switch (mode) {
+            case AppOpsManager.MODE_ALLOWED:
+                return true;
+            case AppOpsManager.MODE_DEFAULT:
+                final String permission = AppOpsManager.opToPermission(op);
+                return permission != null
+                        && context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED;
+            case AppOpsManager.MODE_IGNORED:
+            case AppOpsManager.MODE_ERRORED:
+                return false;
+            default:
+                throw new IllegalStateException(op + " has unknown mode " + mode);
         }
-        return mode == AppOpsManager.MODE_ALLOWED;
     }
 
-    private static boolean noteAppOpAllowingLegacy(Context context,
-            int pid, int uid, String packageName, String op) {
+    /**
+     * Similar to  {@link #noteAppOpPermission(Context, int, int, String, String, String, String)},
+     * but also returns true for legacy apps.
+     */
+    private static boolean noteAppOpAllowingLegacy(@NonNull Context context, int pid, int uid,
+            @NonNull String packageName, @NonNull String op, @Nullable String attributionTag,
+            @Nullable String opMessage) {
         final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
-        final int mode = appOps.noteOpNoThrow(op, uid, packageName);
+        final int mode = appOps.noteOpNoThrow(op, uid, packageName, attributionTag, opMessage);
         switch (mode) {
             case AppOpsManager.MODE_ALLOWED:
                 return true;
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 7309cf3..77da892 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
@@ -162,6 +162,13 @@
     }
 
     @Test
+    public void testCaseInsensitivity() throws Exception {
+        runDeviceTest("testCreateLowerCaseDeleteUpperCase");
+        runDeviceTest("testCreateUpperCaseDeleteLowerCase");
+        runDeviceTest("testCreateMixedCaseDeleteDifferentMixedCase");
+    }
+
+    @Test
     public void testCallingIdentityCacheInvalidation() throws Exception {
         // General IO access
         runDeviceTest("testReadStorageInvalidation");
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 87f7641..c879a6c 100644
--- a/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
+++ b/tests/jni/FuseDaemonTest/src/com/android/tests/fused/FilePathAccessTest.java
@@ -955,6 +955,46 @@
     }
 
     @Test
+    public void testCreateLowerCaseDeleteUpperCase() throws Exception {
+        File upperCase = new File(DOWNLOAD_DIR, "CREATE_LOWER_DELETE_UPPER");
+        File lowerCase = new File(DOWNLOAD_DIR, "create_lower_delete_upper");
+
+        createDeleteCreate(lowerCase, upperCase);
+    }
+
+    @Test
+    public void testCreateUpperCaseDeleteLowerCase() throws Exception {
+        File upperCase = new File(DOWNLOAD_DIR, "CREATE_UPPER_DELETE_LOWER");
+        File lowerCase = new File(DOWNLOAD_DIR, "create_upper_delete_lower");
+
+        createDeleteCreate(upperCase, lowerCase);
+    }
+
+    @Test
+    public void testCreateMixedCaseDeleteDifferentMixedCase() throws Exception {
+        File mixedCase1 = new File(DOWNLOAD_DIR, "CrEaTe_MiXeD_dElEtE_mIxEd");
+        File mixedCase2 = new File(DOWNLOAD_DIR, "cReAtE_mIxEd_DeLeTe_MiXeD");
+
+        createDeleteCreate(mixedCase1, mixedCase2);
+    }
+
+    private void createDeleteCreate(File create, File delete) throws Exception {
+        try {
+            assertThat(create.createNewFile()).isTrue();
+            Thread.sleep(5);
+
+            assertThat(delete.delete()).isTrue();
+            Thread.sleep(5);
+
+            assertThat(create.createNewFile()).isTrue();
+            Thread.sleep(5);
+        } finally {
+            create.delete();
+            create.delete();
+        }
+    }
+
+    @Test
     public void testReadStorageInvalidation() throws Exception {
         testAppOpInvalidation(TEST_APP_C, new File(DCIM_DIR, "read_storage.jpg"),
                 Manifest.permission.READ_EXTERNAL_STORAGE,
diff --git a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
index 425bb67..d581921 100644
--- a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
@@ -59,16 +59,17 @@
 
         assertTrue(checkPermissionSystem(context, pid, uid, packageName));
         assertFalse(checkPermissionBackup(context, pid, uid));
-        assertFalse(checkPermissionManageExternalStorage(context, pid, uid, packageName));
+        assertFalse(
+                checkPermissionManageExternalStorage(context, pid, uid, packageName, null, null));
 
-        assertTrue(checkPermissionReadStorage(context, pid, uid, packageName));
-        assertTrue(checkPermissionWriteStorage(context, pid, uid, packageName));
+        assertTrue(checkPermissionReadStorage(context, pid, uid, packageName, null, null));
+        assertTrue(checkPermissionWriteStorage(context, pid, uid, packageName, null, null));
 
-        assertTrue(checkPermissionReadAudio(context, pid, uid, packageName));
-        assertFalse(checkPermissionWriteAudio(context, pid, uid, packageName));
-        assertTrue(checkPermissionReadVideo(context, pid, uid, packageName));
-        assertFalse(checkPermissionWriteVideo(context, pid, uid, packageName));
-        assertTrue(checkPermissionReadImages(context, pid, uid, packageName));
-        assertFalse(checkPermissionWriteImages(context, pid, uid, packageName));
+        assertTrue(checkPermissionReadAudio(context, pid, uid, packageName, null, null));
+        assertFalse(checkPermissionWriteAudio(context, pid, uid, packageName, null, null));
+        assertTrue(checkPermissionReadVideo(context, pid, uid, packageName, null, null));
+        assertFalse(checkPermissionWriteVideo(context, pid, uid, packageName, null, null));
+        assertTrue(checkPermissionReadImages(context, pid, uid, packageName, null, null));
+        assertFalse(checkPermissionWriteImages(context, pid, uid, packageName, null, null));
     }
 }