Fix case-sensitive comparisons of Android/media

There are several places where we determine things based on whether a
path contains Android/media - but these need to be compared ignoring
case.

Factored out common methods in StringUtils, fixed package name of
StringUtils.

Bug: 195101715
Bug: 209892068
Test: atest FileUtilsTest
Test: TEST_MAPPING
Change-Id: Iaf381750bba12654159e943cc9dfe8e87f92cbcd
Merged-In: Iaf381750bba12654159e943cc9dfe8e87f92cbcd
diff --git a/Android.bp b/Android.bp
index 89e4b95..62c38d5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -120,6 +120,7 @@
         "src/com/android/providers/media/util/ForegroundThread.java",
         "src/com/android/providers/media/util/Logging.java",
         "src/com/android/providers/media/util/MimeUtils.java",
+        "src/com/android/providers/media/util/StringUtils.java",
         "src/com/android/providers/media/playlist/*.java",
     ],
     sdk_version: "module_current",
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index 659da39..b7aab10 100755
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -433,7 +433,8 @@
 static double get_entry_timeout(const string& path, node* node, struct fuse* fuse) {
     string media_path = fuse->GetEffectiveRootPath() + "/Android/media";
     if (fuse->disable_dentry_cache || node->ShouldInvalidate() ||
-        is_package_owned_path(path, fuse->path) || android::base::StartsWith(path, media_path)) {
+        is_package_owned_path(path, fuse->path) ||
+        android::base::StartsWithIgnoreCase(path, media_path)) {
         // We set dentry timeout to 0 for the following reasons:
         // 1. The dentry cache was completely disabled
         // 2.1 Case-insensitive lookups need to invalidate other case-insensitive dentry matches
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 3a12e37..af40b6b 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -235,6 +235,7 @@
 import com.android.providers.media.util.PermissionUtils;
 import com.android.providers.media.util.SQLiteQueryBuilder;
 import com.android.providers.media.util.SpecialFormatDetector;
+import com.android.providers.media.util.StringUtils;
 import com.android.providers.media.util.UserCache;
 import com.android.providers.media.util.XmpInterface;
 
@@ -2271,7 +2272,7 @@
                 supportedPrimaryMimeType = ClipDescription.MIMETYPE_UNKNOWN;
         }
         return (supportedPrimaryMimeType.equalsIgnoreCase(ClipDescription.MIMETYPE_UNKNOWN) ||
-                MimeUtils.startsWithIgnoreCase(mimeType, supportedPrimaryMimeType));
+                StringUtils.startsWithIgnoreCase(mimeType, supportedPrimaryMimeType));
     }
 
     /**
@@ -7599,7 +7600,7 @@
 
         // Offer thumbnail of media, when requested
         final boolean wantsThumb = (opts != null) && opts.containsKey(ContentResolver.EXTRA_SIZE)
-                && MimeUtils.startsWithIgnoreCase(mimeTypeFilter, "image/");
+                && StringUtils.startsWithIgnoreCase(mimeTypeFilter, "image/");
         if (wantsThumb) {
             final ParcelFileDescriptor pfd = ensureThumbnail(uri, signal);
             return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
diff --git a/src/com/android/providers/media/PermissionActivity.java b/src/com/android/providers/media/PermissionActivity.java
index 5b53e7d..50d2557 100644
--- a/src/com/android/providers/media/PermissionActivity.java
+++ b/src/com/android/providers/media/PermissionActivity.java
@@ -73,6 +73,7 @@
 
 import com.android.providers.media.MediaProvider.LocalUriMatcher;
 import com.android.providers.media.util.Metrics;
+import com.android.providers.media.util.StringUtils;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -85,7 +86,6 @@
 import java.util.function.Predicate;
 import java.util.function.ToIntFunction;
 import java.util.stream.Collectors;
-import src.com.android.providers.media.util.StringUtils;
 
 /**
  * Permission dialog that asks for user confirmation before performing a
diff --git a/src/com/android/providers/media/photopicker/ui/AlbumGridHolder.java b/src/com/android/providers/media/photopicker/ui/AlbumGridHolder.java
index 67fed45..1585d3b 100644
--- a/src/com/android/providers/media/photopicker/ui/AlbumGridHolder.java
+++ b/src/com/android/providers/media/photopicker/ui/AlbumGridHolder.java
@@ -28,12 +28,12 @@
 
 import com.android.providers.media.R;
 import com.android.providers.media.photopicker.data.model.Category;
+import com.android.providers.media.util.StringUtils;
 
 import java.text.NumberFormat;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
-import src.com.android.providers.media.util.StringUtils;
 
 /**
  * ViewHolder of a album item within a RecyclerView.
diff --git a/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java b/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
index 5e8d2ac..e0ced0c 100644
--- a/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
@@ -35,12 +35,12 @@
 import com.android.providers.media.photopicker.data.model.Category.CategoryType;
 import com.android.providers.media.photopicker.data.model.Item;
 import com.android.providers.media.photopicker.util.LayoutModeUtils;
+import com.android.providers.media.util.StringUtils;
 
 import com.google.android.material.snackbar.Snackbar;
 
 import java.text.NumberFormat;
 import java.util.Locale;
-import src.com.android.providers.media.util.StringUtils;
 
 /**
  * Photos tab fragment for showing the photos
diff --git a/src/com/android/providers/media/util/FileUtils.java b/src/com/android/providers/media/util/FileUtils.java
index b907e43..8da9e97 100644
--- a/src/com/android/providers/media/util/FileUtils.java
+++ b/src/com/android/providers/media/util/FileUtils.java
@@ -736,8 +736,8 @@
                 extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
             }
 
-            if (MimeUtils.equalIgnoreCase(mimeType, mimeTypeFromExt)
-                    || MimeUtils.equalIgnoreCase(ext, extFromMimeType)) {
+            if (StringUtils.equalIgnoreCase(mimeType, mimeTypeFromExt)
+                    || StringUtils.equalIgnoreCase(ext, extFromMimeType)) {
                 // Extension maps back to requested MIME type; allow it
             } else {
                 // No match; insist that create file matches requested MIME
@@ -1167,7 +1167,7 @@
         if (relativePath != null) {
             final String externalMediaDir = (crossUserRoot == null || crossUserRoot.isEmpty())
                     ? "Android/media" : crossUserRoot + "/Android/media";
-            return relativePath.startsWith(externalMediaDir);
+            return StringUtils.startsWithIgnoreCase(relativePath, externalMediaDir);
         }
         return false;
     }
diff --git a/src/com/android/providers/media/util/MimeUtils.java b/src/com/android/providers/media/util/MimeUtils.java
index 38c3635..65f3e83 100644
--- a/src/com/android/providers/media/util/MimeUtils.java
+++ b/src/com/android/providers/media/util/MimeUtils.java
@@ -29,23 +29,6 @@
 import java.util.Objects;
 
 public class MimeUtils {
-    /**
-     * Variant of {@link Objects#equal(Object, Object)} but which tests with
-     * case-insensitivity.
-     */
-    public static boolean equalIgnoreCase(@Nullable String a, @Nullable String b) {
-        return (a != null) && a.equalsIgnoreCase(b);
-    }
-
-    /**
-     * Variant of {@link String#startsWith(String)} but which tests with
-     * case-insensitivity.
-     */
-    public static boolean startsWithIgnoreCase(@Nullable String target, @Nullable String other) {
-        if (target == null || other == null) return false;
-        if (other.length() > target.length()) return false;
-        return target.regionMatches(true, 0, other, 0, other.length());
-    }
 
     /**
      * Resolve the MIME type of the given file, returning
@@ -114,17 +97,17 @@
 
     public static boolean isAudioMimeType(@Nullable String mimeType) {
         if (mimeType == null) return false;
-        return startsWithIgnoreCase(mimeType, "audio/");
+        return StringUtils.startsWithIgnoreCase(mimeType, "audio/");
     }
 
     public static boolean isVideoMimeType(@Nullable String mimeType) {
         if (mimeType == null) return false;
-        return startsWithIgnoreCase(mimeType, "video/");
+        return StringUtils.startsWithIgnoreCase(mimeType, "video/");
     }
 
     public static boolean isImageMimeType(@Nullable String mimeType) {
         if (mimeType == null) return false;
-        return startsWithIgnoreCase(mimeType, "image/");
+        return StringUtils.startsWithIgnoreCase(mimeType, "image/");
     }
 
     public static boolean isImageOrVideoMediaType(int mediaType) {
@@ -170,7 +153,7 @@
     public static boolean isDocumentMimeType(@Nullable String mimeType) {
         if (mimeType == null) return false;
 
-        if (startsWithIgnoreCase(mimeType, "text/")) return true;
+        if (StringUtils.startsWithIgnoreCase(mimeType, "text/")) return true;
 
         switch (mimeType.toLowerCase(Locale.ROOT)) {
             case "application/epub+zip":
diff --git a/src/com/android/providers/media/util/StringUtils.java b/src/com/android/providers/media/util/StringUtils.java
index f73a58a..b6f70e0 100644
--- a/src/com/android/providers/media/util/StringUtils.java
+++ b/src/com/android/providers/media/util/StringUtils.java
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
-package src.com.android.providers.media.util;
+package com.android.providers.media.util;
 
 import android.icu.text.MessageFormat;
-import java.io.File;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
+
 import android.content.res.Resources;
 
+import androidx.annotation.Nullable;
+
 public class StringUtils {
 
   /**
@@ -37,4 +40,23 @@
     arguments.put("count", count);
     return msgFormat.format(arguments);
   }
+
+
+  /**
+   * Variant of {@link String#startsWith(String)} but which tests with
+   * case-insensitivity.
+   */
+  public static boolean startsWithIgnoreCase(@Nullable String target, @Nullable String other) {
+    if (target == null || other == null) return false;
+    if (other.length() > target.length()) return false;
+    return target.regionMatches(true, 0, other, 0, other.length());
+  }
+
+  /**
+   * Variant of {@link Objects#equal(Object, Object)} but which tests with
+   * case-insensitivity.
+   */
+  public static boolean equalIgnoreCase(@Nullable String a, @Nullable String b) {
+      return (a != null) && a.equalsIgnoreCase(b);
+  }
 }
diff --git a/tests/src/com/android/providers/media/util/FileUtilsTest.java b/tests/src/com/android/providers/media/util/FileUtilsTest.java
index 5d70aee..398f431 100644
--- a/tests/src/com/android/providers/media/util/FileUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/FileUtilsTest.java
@@ -944,6 +944,7 @@
                 "/storage/0000-0000/AppClone/"
         }) {
             assertTrue(isExternalMediaDirectory(prefix + "Android/media/foo.jpg", "AppClone"));
+            assertTrue(isExternalMediaDirectory(prefix + "android/mEdia/foo.jpg", "AppClone"));
             assertFalse(isExternalMediaDirectory(prefix + "Android/media/foo.jpg", "NotAppClone"));
         }
     }
diff --git a/tests/src/com/android/providers/media/util/MimeUtilsTest.java b/tests/src/com/android/providers/media/util/MimeUtilsTest.java
index b57b5c5..9491a98 100644
--- a/tests/src/com/android/providers/media/util/MimeUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/MimeUtilsTest.java
@@ -16,9 +16,6 @@
 
 package com.android.providers.media.util;
 
-import static com.android.providers.media.util.MimeUtils.equalIgnoreCase;
-import static com.android.providers.media.util.MimeUtils.startsWithIgnoreCase;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -42,30 +39,6 @@
     }
 
     @Test
-    public void testEqualIgnoreCase() throws Exception {
-        assertTrue(equalIgnoreCase("image/jpg", "image/jpg"));
-        assertTrue(equalIgnoreCase("image/jpg", "Image/Jpg"));
-
-        assertFalse(equalIgnoreCase("image/jpg", "image/png"));
-        assertFalse(equalIgnoreCase("image/jpg", null));
-        assertFalse(equalIgnoreCase(null, "image/jpg"));
-        assertFalse(equalIgnoreCase(null, null));
-    }
-
-    @Test
-    public void testStartsWithIgnoreCase() throws Exception {
-        assertTrue(startsWithIgnoreCase("image/jpg", "image/"));
-        assertTrue(startsWithIgnoreCase("Image/Jpg", "image/"));
-
-        assertFalse(startsWithIgnoreCase("image/", "image/jpg"));
-
-        assertFalse(startsWithIgnoreCase("image/jpg", "audio/"));
-        assertFalse(startsWithIgnoreCase("image/jpg", null));
-        assertFalse(startsWithIgnoreCase(null, "audio/"));
-        assertFalse(startsWithIgnoreCase(null, null));
-    }
-
-    @Test
     public void testResolveMimeType() throws Exception {
         assertEquals("image/jpeg",
                 MimeUtils.resolveMimeType(new File("foo.jpg")));
diff --git a/tests/src/com/android/providers/media/util/StringUtilsTest.java b/tests/src/com/android/providers/media/util/StringUtilsTest.java
new file mode 100644
index 0000000..51a571e
--- /dev/null
+++ b/tests/src/com/android/providers/media/util/StringUtilsTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media.util;
+
+import static com.android.providers.media.util.StringUtils.equalIgnoreCase;
+import static com.android.providers.media.util.StringUtils.startsWithIgnoreCase;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class StringUtilsTest {
+    @Test
+    public void testEqualIgnoreCase() throws Exception {
+        assertTrue(equalIgnoreCase("image/jpg", "image/jpg"));
+        assertTrue(equalIgnoreCase("image/jpg", "Image/Jpg"));
+
+        assertFalse(equalIgnoreCase("image/jpg", "image/png"));
+        assertFalse(equalIgnoreCase("image/jpg", null));
+        assertFalse(equalIgnoreCase(null, "image/jpg"));
+        assertFalse(equalIgnoreCase(null, null));
+    }
+
+    @Test
+    public void testStartsWithIgnoreCase() throws Exception {
+        assertTrue(startsWithIgnoreCase("image/jpg", "image/"));
+        assertTrue(startsWithIgnoreCase("Image/Jpg", "image/"));
+
+        assertFalse(startsWithIgnoreCase("image/", "image/jpg"));
+
+        assertFalse(startsWithIgnoreCase("image/jpg", "audio/"));
+        assertFalse(startsWithIgnoreCase("image/jpg", null));
+        assertFalse(startsWithIgnoreCase(null, "audio/"));
+        assertFalse(startsWithIgnoreCase(null, null));
+    }
+}