Introduce vendor.mime.types

Like mime.types and android.mime.types, this file specifies mappings
between MIME types and file extensions. Unlike those files, it can
only be used to define _additional_ mapping but not modify (change,
remove) any mappings defined by those files.

This is done by prepending '?' to every line element from
vendor.mime.types that doesn't already have one; when there is a
leading "?", it is ignored so that it's okay to move a line from
{android,vendor}.mime.types without necessarily changing it.

Test: Checked manually that vendor.mime.types works as expected.
  Specifically, after adding these lines to vendor.mime.type:

    audio/mpeg testmpeg
    audio/testmpeg mp3
    ?mime/foo ?fooext

  the following test passes:

    MimeTypeMap map = MimeTypeMap.getSingleton();
    // Original mapping is unchanged
    assertEquals("mp3", map.getExtensionFromMimeType("audio/mpeg"));
    assertEquals("audio/mpeg", map.getMimeTypeFromExtension("mp3"));

    // Map from the key to existing value is added
    assertEquals("audio/mpeg", map.getMimeTypeFromExtension("testmpeg"));
    assertEquals("mp3", map.getExtensionFromMimeType("audio/testmpeg"));

    // Completely new mapping is added both ways
    assertEquals("mime/foo", map.getMimeTypeFromExtension("fooext"));
    assertEquals("fooext", map.getExtensionFromMimeType("mime/foo"));

Bug: 141842825
Change-Id: Iaf918ce39324709ff58a8e0f9612e4827a673323
diff --git a/mime/java/android/content/type/DefaultMimeMapFactory.java b/mime/java/android/content/type/DefaultMimeMapFactory.java
index 545fb3c..56b234f 100644
--- a/mime/java/android/content/type/DefaultMimeMapFactory.java
+++ b/mime/java/android/content/type/DefaultMimeMapFactory.java
@@ -21,6 +21,7 @@
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.regex.Pattern;
@@ -44,20 +45,17 @@
      * Android's default mapping between MIME types and extensions.
      */
     public static MimeMap create() {
-        return parseFromResources("/mime.types", "/android.mime.types");
+        MimeMap.Builder builder = MimeMap.builder();
+        parseTypes(builder, true, "/mime.types");
+        parseTypes(builder, true, "/android.mime.types");
+        parseTypes(builder, false, "/vendor.mime.types");
+        return builder.build();
     }
 
     private static final Pattern SPLIT_PATTERN = Pattern.compile("\\s+");
 
-    static MimeMap parseFromResources(String... resourceNames) {
-        MimeMap.Builder builder = MimeMap.builder();
-        for (String resourceName : resourceNames) {
-            parseTypes(builder, resourceName);
-        }
-        return builder.build();
-    }
-
-    private static void parseTypes(MimeMap.Builder builder, String resource) {
+    private static void parseTypes(MimeMap.Builder builder, boolean allowOverwrite,
+            String resource) {
         try (BufferedReader r = new BufferedReader(
                 new InputStreamReader(DefaultMimeMapFactory.class.getResourceAsStream(resource)))) {
             String line;
@@ -71,6 +69,12 @@
                     continue;
                 }
                 List<String> specs = Arrays.asList(SPLIT_PATTERN.split(line));
+                if (!allowOverwrite) {
+                    // Pretend that the mimeType and each file extension listed in the line
+                    // carries a "?" prefix, which means that it can add new mappings but
+                    // not modify existing mappings (putIfAbsent() semantics).
+                    specs = ensurePrefix("?", specs);
+                }
                 builder.put(specs.get(0), specs.subList(1, specs.size()));
             }
         } catch (IOException | RuntimeException e) {
@@ -78,4 +82,15 @@
         }
     }
 
+    private static List<String> ensurePrefix(String prefix, List<String> strings) {
+        List<String> result = new ArrayList<>(strings.size());
+        for (String s : strings) {
+            if (!s.startsWith(prefix)) {
+                s = prefix + s;
+            }
+            result.add(s);
+        }
+        return result;
+    }
+
 }