Revert "Move default MimeMap implementation to frameworks."

This reverts commit d9e06a7351c25bf3275cffc382e3e7f8c87e6079.

Reason for revert: Caused slower app startup (I don't know why).

Change-Id: I7865dd97ee202e26236498505af3d746143f74e4
diff --git a/JavaLibrary.bp b/JavaLibrary.bp
index c85cfe5..8a5813b 100644
--- a/JavaLibrary.bp
+++ b/JavaLibrary.bp
@@ -52,6 +52,7 @@
     ],
     path: "luni/src/main/java/",
     srcs: [
+        "luni/src/main/java/libcore/net/android.mime.types",
         "luni/src/main/java/java/util/logging/logging.properties",
         "luni/src/main/java/java/security/security.properties",
     ],
@@ -71,6 +72,7 @@
 core_resources = [
     ":core-luni-resources",
     ":core-ojluni-resources",
+    ":debian.mime.types",
 ]
 
 // The source files that go into core-oj.
diff --git a/luni/src/main/java/libcore/net/MimeMap.java b/luni/src/main/java/libcore/net/MimeMap.java
index 107ccab..afb6cbd 100644
--- a/luni/src/main/java/libcore/net/MimeMap.java
+++ b/luni/src/main/java/libcore/net/MimeMap.java
@@ -16,10 +16,9 @@
 
 package libcore.net;
 
-import java.util.HashMap;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
 import libcore.util.NonNull;
 import libcore.util.Nullable;
 
@@ -29,63 +28,30 @@
  */
 @libcore.api.CorePlatformApi
 public abstract class MimeMap {
-    private static volatile MimeMap defaultInstance = new DefaultImpl();
-
-    /**
-     * A basic implementation of MimeMap used if a new default isn't explicitly
-     * {@link MimeMap#setDefault(MimeMap) installed}. Hard-codes enough mappings
-     * to satisfy libcore tests. Android framework code is expected to replace
-     * this implementation during runtime initialization.
-     */
-    private static class DefaultImpl extends MimeMap {
-        private final Map<String, String> mimeToExt = new HashMap<>();
-        private final Map<String, String> extToMime = new HashMap<>();
-
-        private DefaultImpl() {
-            put("application/pdf", "pdf");
-            put("image/jpeg", "jpg");
-            put("image/x-ms-bmp", "bmp");
-            put("text/html", "htm", "html");
-            put("text/plain", "text", "txt");
-            put("text/x-java", "java");
-        }
-
-        private void put(String mime, String... exts) {
-            mimeToExt.put(mime, exts[0]);
-            for (String ext : exts) {
-                extToMime.put(ext, mime);
-            }
-        }
-
-        @Override
-        protected @Nullable String guessMimeTypeFromLowerCaseExtension(@NonNull String extension) {
-            return extToMime.get(extension);
-        }
-
-        @Override
-        protected @Nullable String guessExtensionFromLowerCaseMimeType(@NonNull String mimeType) {
-            return mimeToExt.get(mimeType);
-        }
-    }
-
-    @libcore.api.CorePlatformApi
-    protected MimeMap() {
-    }
+    private static AtomicReference<MimeMap> defaultHolder = new AtomicReference<>(
+            MimeMapImpl.parseFromResources("/mime.types", "android.mime.types"));
 
     /**
      * @return The system's current default {@link MimeMap}.
      */
     @libcore.api.CorePlatformApi
     public static @NonNull MimeMap getDefault() {
-        return defaultInstance;
+        return defaultHolder.get();
     }
 
     /**
-     * Sets the system's default {@link MimeMap} to be {@code mimeMap}.
+     * Atomically sets the system's default {@link MimeMap} to be {@code update} if the
+     * current value {@code == expect}.
+     *
+     * @param expect the expected current default {@link MimeMap}; must not be null.
+     * @param update the new default {@link MimeMap} to set; must not be null.
+     * @return whether the update was successful.
      */
     @libcore.api.CorePlatformApi
-    public static void setDefault(@NonNull MimeMap mimeMap) {
-        defaultInstance = Objects.requireNonNull(mimeMap);
+    public static boolean compareAndSetDefault(@NonNull MimeMap expect, @NonNull MimeMap update) {
+        Objects.requireNonNull(expect);
+        Objects.requireNonNull(update);
+        return defaultHolder.compareAndSet(expect, update);
     }
 
     /**
@@ -174,13 +140,11 @@
     /**
      * Returns the canonical (lowercase) form of the given extension or MIME type.
      */
-    @libcore.api.CorePlatformApi
-    public static @NonNull String toLowerCase(@NonNull String s) {
+    static @NonNull String toLowerCase(@NonNull String s) {
         return s.toLowerCase(Locale.ROOT);
     }
 
-    @libcore.api.CorePlatformApi
-    public static boolean isNullOrEmpty(@Nullable String s) {
+    static boolean isNullOrEmpty(@Nullable String s) {
         return s == null || s.isEmpty();
     }
 
diff --git a/luni/src/main/java/libcore/net/MimeMapImpl.java b/luni/src/main/java/libcore/net/MimeMapImpl.java
new file mode 100644
index 0000000..a10dd6a
--- /dev/null
+++ b/luni/src/main/java/libcore/net/MimeMapImpl.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2019 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 libcore.net;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+class MimeMapImpl extends MimeMap {
+
+    private static final Pattern splitPattern = Pattern.compile("\\s+");
+
+    /**
+     * Note: These maps only contain lowercase keys/values, regarded as the
+     * {@link #toLowerCase(String) canonical form}.
+     *
+     * <p>This is the case for both extensions and MIME types. The mime.types
+     * data file contains examples of mixed-case MIME types, but some applications
+     * use the lowercase version of these same types. RFC 2045 section 2 states
+     * that MIME types are case insensitive.
+     */
+    private final Map<String, String> mimeTypeToExtension;
+    private final Map<String, String> extensionToMimeType;
+
+    public MimeMapImpl(Map<String, String> mimeTypeToExtension,
+            Map<String, String> extensionToMimeType) {
+        this.mimeTypeToExtension = new HashMap<>(mimeTypeToExtension);
+        for (Map.Entry<String, String> entry : mimeTypeToExtension.entrySet()) {
+            checkValidMimeType(entry.getKey());
+            checkValidExtension(entry.getValue());
+        }
+        this.extensionToMimeType = new HashMap<>(extensionToMimeType);
+        for (Map.Entry<String, String> entry : extensionToMimeType.entrySet()) {
+            checkValidExtension(entry.getKey());
+            checkValidMimeType(entry.getValue());
+        }
+    }
+
+    private static void checkValidMimeType(String s) {
+        if (MimeMap.isNullOrEmpty(s) || !s.equals(MimeMap.toLowerCase(s))) {
+            throw new IllegalArgumentException("Invalid MIME type: " + s);
+        }
+    }
+
+    private static void checkValidExtension(String s) {
+        if (isNullOrEmpty(s) || !s.equals(toLowerCase(s))) {
+            throw new IllegalArgumentException("Invalid extension: " + s);
+        }
+    }
+
+    static MimeMapImpl parseFromResources(String... resourceNames) {
+        Map<String, String> mimeTypeToExtension = new HashMap<>();
+        Map<String, String> extensionToMimeType = new HashMap<>();
+        for (String resourceName : resourceNames) {
+            parseTypes(mimeTypeToExtension, extensionToMimeType, resourceName);
+        }
+        return new MimeMapImpl(mimeTypeToExtension, extensionToMimeType);
+    }
+
+    /**
+     * An element of a *mime.types file: A MIME type or an extension, with an optional
+     * prefix of "?" (if not overriding an earlier value).
+     */
+    private static class Element {
+        public final boolean keepExisting;
+        public final String s;
+
+        public Element(boolean keepExisting, String value) {
+            this.keepExisting = keepExisting;
+            this.s = toLowerCase(value);
+            if (value.isEmpty()) {
+                throw new IllegalArgumentException();
+            }
+        }
+
+        public String toString() {
+            return keepExisting ? ("?" + s) : s;
+        }
+    }
+
+    private static String maybePut(Map<String, String> map, Element keyElement, String value) {
+        if (keyElement.keepExisting) {
+            return map.putIfAbsent(keyElement.s, value);
+        } else {
+            return map.put(keyElement.s, value);
+        }
+    }
+
+    private static void parseTypes(Map<String, String> mimeTypeToExtension,
+            Map<String, String> extensionToMimeType, String resource) {
+        try (BufferedReader r = new BufferedReader(
+                new InputStreamReader(MimeMap.class.getResourceAsStream(resource)))) {
+            String line;
+            while ((line = r.readLine()) != null) {
+                int commentPos = line.indexOf('#');
+                if (commentPos >= 0) {
+                    line = line.substring(0, commentPos);
+                }
+                line = line.trim();
+                // The first time a MIME type is encountered it is mapped to the first extension
+                // listed in its line. The first time an extension is encountered it is mapped
+                // to the MIME type.
+                //
+                // When encountering a previously seen MIME type or extension, then by default
+                // the later ones override earlier mappings (put() semantics); however if a MIME
+                // type or extension is prefixed with '?' then any earlier mapping _from_ that
+                // MIME type / extension is kept (putIfAbsent() semantics).
+                final String[] split = splitPattern.split(line);
+                if (split.length <= 1) {
+                    // Need mimeType + at least one extension to make a mapping.
+                    // "mime.types" files may also contain lines with just a mimeType without
+                    // an extension but we skip them as they provide no mapping info.
+                    continue;
+                }
+                List<Element> lineElements = new ArrayList<>(split.length);
+                for (String s : split) {
+                    boolean keepExisting = s.startsWith("?");
+                    if (keepExisting) {
+                        s = s.substring(1);
+                    }
+                    if (s.isEmpty()) {
+                        throw new IllegalArgumentException("Invalid entry in '" + line + "'");
+                    }
+                    lineElements.add(new Element(keepExisting, s));
+                }
+
+                // MIME type -> first extension (one mapping)
+                // This will override any earlier mapping from this MIME type to another
+                // extension, unless this MIME type was prefixed with '?'.
+                Element mimeElement = lineElements.get(0);
+                List<Element> extensionElements = lineElements.subList(1, lineElements.size());
+                String firstExtension = extensionElements.get(0).s;
+                maybePut(mimeTypeToExtension, mimeElement, firstExtension);
+
+                // extension -> MIME type (one or more mappings).
+                // This will override any earlier mapping from this extension to another
+                // MIME type, unless this extension was prefixed with '?'.
+                for (Element extensionElement : extensionElements) {
+                    maybePut(extensionToMimeType, extensionElement, mimeElement.s);
+                }
+            }
+        } catch (IOException | RuntimeException e) {
+            throw new RuntimeException("Failed to parse " + resource, e);
+        }
+    }
+
+    @Override
+    protected String guessExtensionFromLowerCaseMimeType(String mimeType) {
+        return mimeTypeToExtension.get(mimeType);
+    }
+
+    @Override
+    protected String guessMimeTypeFromLowerCaseExtension(String extension) {
+        return extensionToMimeType.get(extension);
+    }
+}
diff --git a/luni/src/main/java/libcore/net/android.mime.types b/luni/src/main/java/libcore/net/android.mime.types
new file mode 100644
index 0000000..1ca912e
--- /dev/null
+++ b/luni/src/main/java/libcore/net/android.mime.types
@@ -0,0 +1,146 @@
+
+###############################################################################
+#
+# Android-specific MIME type <-> extension mappings
+#
+# Each line below defines an mapping from one MIME type to the first of the
+# listed extensions, and from listed extension back to the MIME type.
+# A mapping overrides any previous mapping _from_ that same MIME type or
+# extension (put() semantics), unless that MIME type / extension is prefixed with '?'
+# (putIfAbsent() semantics).
+#
+#
+###############################################################################
+#
+# EXAMPLES
+#
+# A line of the form:
+#
+#    ?mime ext1 ?ext2 ext3
+#
+# affects the current mappings along the lines of the following pseudo code:
+#
+#    mimeToExt.putIfAbsent("mime", "ext1");
+#    extToMime.put("ext1", "mime");
+#    extToMime.putIfAbsent("ext2", "mime");
+#    extToMime.put("ext3", "mime");
+#
+# The line:
+#
+#     ?text/plain txt
+#
+# leaves any earlier mapping for "text/plain" untouched, or maps that MIME type
+# to the file extension ".txt" if there is no earlier mapping. The line also
+# sets the mapping from file extension ".txt" to be the MIME type "text/plain",
+# regardless of whether a previous mapping existed.
+#
+###############################################################################
+
+
+# File extensions that Android wants to override to point to the given MIME type.
+#
+# After processing a line of the form:
+# ?<mimeType> <extension1> <extension2>
+# If <mimeType> was not already mapped to an extension then it will be
+# mapped to <extension1>.
+# <extension1> and <extension2> are mapped (or remapped) to <mimeType>.
+
+?application/epub+zip epub
+?application/pkix-cert cer
+?application/rss+xml rss
+?application/vnd.android.ota ota
+?application/vnd.apple.mpegurl m3u8
+?application/vnd.ms-pki.stl stl
+?application/vnd.ms-powerpoint pot
+?application/vnd.ms-wpl wpl
+?application/vnd.stardivision.impress sdp
+?application/vnd.stardivision.writer vor
+?application/vnd.youtube.yt yt
+?application/x-android-drm-fl fl
+?application/x-flac flac
+?application/x-font pcf
+?application/x-mpegurl m3u m3u8
+?application/x-pem-file pem
+?application/x-pkcs12 p12 pfx
+?application/x-webarchive webarchive
+?application/x-webarchive-xml webarchivexml
+?application/x-x509-server-cert crt
+?application/x-x509-user-cert crt
+
+?audio/3gpp 3gpp
+?audio/aac-adts aac
+?audio/imelody imy
+?audio/midi rtttl xmf
+?audio/mobile-xmf mxmf
+?audio/mp4 m4a
+?audio/mpegurl m3u
+?audio/sp-midi smf
+?audio/x-matroska mka
+?audio/x-pn-realaudio ra
+
+?image/bmp bmp
+?image/heic heic
+?image/heic-sequence heics
+?image/heif heif hif
+?image/heif-sequence heifs
+?image/ico cur
+?image/webp webp
+?image/x-adobe-dng dng
+?image/x-fuji-raf raf
+?image/x-icon ico
+?image/x-nikon-nrw nrw
+?image/x-panasonic-rw2 rw2
+?image/x-pentax-pef pef
+?image/x-samsung-srw srw
+?image/x-sony-arw arw
+
+?text/comma-separated-values csv
+?text/plain diff po
+?text/rtf rtf
+?text/text phps
+?text/xml xml
+?text/x-vcard vcf
+
+?video/3gpp2 3gpp2 3g2
+?video/3gpp 3gpp
+?video/avi avi
+?video/m4v m4v
+?video/mp2p mpeg
+?video/mp2t m2ts mts
+?video/mp2ts ts
+?video/vnd.youtube.yt yt
+?video/x-webex wrf
+
+# Optional additions that should not override any previous mapping.
+
+?application/x-wifi-config ?xml
+
+# Special cases where Android has a strong opinion about mappings, so we
+# define them very last and make them override in both directions (no "?").
+#
+# Lines here are of the form:
+# <mimeType> <extension1> <extension2> ...
+#
+# After processing each line,
+#   <mimeType> is mapped to <extension1>
+#   <extension1>, <extension2>, ... are all mapped to <mimeType>
+# This overrides any mappings for this <mimeType> / for these extensions
+# that may have been defined earlier.
+
+application/pgp-signature pgp
+application/x-x509-ca-cert crt
+audio/aac aac
+audio/basic snd
+audio/flac flac
+audio/midi rtx
+audio/mpeg mp3 m4a m4r
+audio/x-mpegurl m3u m3u8
+image/jpeg jpg
+image/x-ms-bmp bmp
+text/plain txt
+text/x-c++hdr hpp
+text/x-c++src cpp
+video/3gpp 3gpp
+video/mpeg mpeg
+video/quicktime mov
+video/x-matroska mkv
diff --git a/luni/src/test/java/libcore/libcore/net/MimeMapTest.java b/luni/src/test/java/libcore/libcore/net/MimeMapTest.java
index c435264..de98309 100644
--- a/luni/src/test/java/libcore/libcore/net/MimeMapTest.java
+++ b/luni/src/test/java/libcore/libcore/net/MimeMapTest.java
@@ -20,19 +20,25 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import libcore.net.MimeMap;
-import libcore.util.NonNull;
+import java.util.Locale;
+import java.util.Objects;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+import libcore.net.MimeMap;
+import libcore.util.NonNull;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 
 public class MimeMapTest {
 
@@ -46,9 +52,11 @@
     }
 
     private TestMimeMap mimeMap;
+    private MimeMap defaultMimeMap;
 
     @Before public void setUp() {
         mimeMap = mock(TestMimeMap.class);
+        defaultMimeMap = MimeMap.getDefault();
     }
 
     @After public void tearDown() {
@@ -95,25 +103,224 @@
         verify(mimeMap, times(2)).guessMimeTypeFromLowerCaseExtension("test");
     }
 
-    @Test public void setDefault() {
-        MimeMap defaultMimeMap = MimeMap.getDefault();
+    @Test public void compareAndSetDefault() {
         MimeMap otherMimeMap = mock(TestMimeMap.class);
-        MimeMap.setDefault(otherMimeMap);
+        MimeMap defaultMimeMap = MimeMap.getDefault();
+        assertTrue(MimeMap.compareAndSetDefault(defaultMimeMap, mimeMap));
         try {
-            assertEquals(otherMimeMap, MimeMap.getDefault());
+            assertNotNull(defaultMimeMap);
+            assertEquals(mimeMap, MimeMap.getDefault());
+            assertFalse(MimeMap.compareAndSetDefault(defaultMimeMap, otherMimeMap));
         } finally {
-            MimeMap.setDefault(defaultMimeMap);
+            assertTrue(MimeMap.compareAndSetDefault(mimeMap, defaultMimeMap));
         }
     }
 
-    @Test public void setDefault_null() {
+    @Test public void compareAndSetDefault_null() {
         MimeMap defaultMimeMap = MimeMap.getDefault();
         try {
-            MimeMap.setDefault(null);
+            MimeMap.compareAndSetDefault(defaultMimeMap, null);
             fail();
         } catch (NullPointerException expected) {
-            assertEquals(defaultMimeMap, MimeMap.getDefault());
+        }
+
+        try {
+            MimeMap.compareAndSetDefault(null, defaultMimeMap);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+
+        // For comparison, this does not throw (but has no effect):
+        MimeMap.compareAndSetDefault(defaultMimeMap, defaultMimeMap);
+        assertEquals(defaultMimeMap, MimeMap.getDefault());
+    }
+
+    @Test public void defaultMap_15715370() {
+        assertEquals("audio/flac", defaultMimeMap.guessMimeTypeFromExtension("flac"));
+        assertEquals("flac", defaultMimeMap.guessExtensionFromMimeType("audio/flac"));
+        assertEquals("flac", defaultMimeMap.guessExtensionFromMimeType("application/x-flac"));
+    }
+
+    // https://code.google.com/p/android/issues/detail?id=78909
+    @Test public void defaultMap_78909() {
+        assertEquals("mka", defaultMimeMap.guessExtensionFromMimeType("audio/x-matroska"));
+        assertEquals("mkv", defaultMimeMap.guessExtensionFromMimeType("video/x-matroska"));
+    }
+
+    @Test public void defaultMap_16978217() {
+        assertEquals("image/x-ms-bmp", defaultMimeMap.guessMimeTypeFromExtension("bmp"));
+        assertEquals("image/x-icon", defaultMimeMap.guessMimeTypeFromExtension("ico"));
+        assertEquals("video/mp2ts", defaultMimeMap.guessMimeTypeFromExtension("ts"));
+    }
+
+    @Test public void testCommon() {
+        assertEquals("audio/mpeg", defaultMimeMap.guessMimeTypeFromExtension("mp3"));
+        assertEquals("image/png", defaultMimeMap.guessMimeTypeFromExtension("png"));
+        assertEquals("application/zip", defaultMimeMap.guessMimeTypeFromExtension("zip"));
+
+        assertEquals("mp3", defaultMimeMap.guessExtensionFromMimeType("audio/mpeg"));
+        assertEquals("png", defaultMimeMap.guessExtensionFromMimeType("image/png"));
+        assertEquals("zip", defaultMimeMap.guessExtensionFromMimeType("application/zip"));
+    }
+
+    @Test public void defaultMap_18390752() {
+        assertEquals("jpg", defaultMimeMap.guessExtensionFromMimeType("image/jpeg"));
+    }
+
+    @Test public void defaultMap_30207891() {
+        assertTrue(defaultMimeMap.hasMimeType("IMAGE/PNG"));
+        assertTrue(defaultMimeMap.hasMimeType("IMAGE/png"));
+        assertFalse(defaultMimeMap.hasMimeType(""));
+        assertEquals("png", defaultMimeMap.guessExtensionFromMimeType("IMAGE/PNG"));
+        assertEquals("png", defaultMimeMap.guessExtensionFromMimeType("IMAGE/png"));
+        assertNull(defaultMimeMap.guessMimeTypeFromExtension(""));
+        assertNull(defaultMimeMap.guessMimeTypeFromExtension("doesnotexist"));
+        assertTrue(defaultMimeMap.hasExtension("PNG"));
+        assertTrue(defaultMimeMap.hasExtension("PnG"));
+        assertFalse(defaultMimeMap.hasExtension(""));
+        assertFalse(defaultMimeMap.hasExtension(".png"));
+        assertEquals("image/png", defaultMimeMap.guessMimeTypeFromExtension("PNG"));
+        assertEquals("image/png", defaultMimeMap.guessMimeTypeFromExtension("PnG"));
+        assertNull(defaultMimeMap.guessMimeTypeFromExtension(".png"));
+        assertNull(defaultMimeMap.guessMimeTypeFromExtension(""));
+        assertNull(defaultMimeMap.guessExtensionFromMimeType("doesnotexist"));
+    }
+
+    @Test public void defaultMap_30793548() {
+        assertEquals("video/3gpp", defaultMimeMap.guessMimeTypeFromExtension("3gpp"));
+        assertEquals("video/3gpp", defaultMimeMap.guessMimeTypeFromExtension("3gp"));
+        assertEquals("video/3gpp2", defaultMimeMap.guessMimeTypeFromExtension("3gpp2"));
+        assertEquals("video/3gpp2", defaultMimeMap.guessMimeTypeFromExtension("3g2"));
+    }
+
+    @Test public void defaultMap_37167977() {
+        // https://tools.ietf.org/html/rfc5334#section-10.1
+        assertEquals("audio/ogg", defaultMimeMap.guessMimeTypeFromExtension("ogg"));
+        assertEquals("audio/ogg", defaultMimeMap.guessMimeTypeFromExtension("oga"));
+        assertEquals("audio/ogg", defaultMimeMap.guessMimeTypeFromExtension("spx"));
+        assertEquals("video/ogg", defaultMimeMap.guessMimeTypeFromExtension("ogv"));
+    }
+
+    @Test public void defaultMap_70851634_mimeTypeFromExtension() {
+        assertEquals("video/vnd.youtube.yt", defaultMimeMap.guessMimeTypeFromExtension("yt"));
+    }
+
+    @Test public void defaultMap_70851634_extensionFromMimeType() {
+        assertEquals("yt", defaultMimeMap.guessExtensionFromMimeType("video/vnd.youtube.yt"));
+        assertEquals("yt", defaultMimeMap.guessExtensionFromMimeType("application/vnd.youtube.yt"));
+    }
+
+    @Test public void defaultMap_112162449_audio() {
+        // According to https://en.wikipedia.org/wiki/M3U#Internet_media_types
+        // this is a giant mess, so we pick "audio/x-mpegurl" because a similar
+        // playlist format uses "audio/x-scpls".
+        assertMimeTypeFromExtension("audio/x-mpegurl", "m3u");
+        assertMimeTypeFromExtension("audio/x-mpegurl", "m3u8");
+        assertExtensionFromMimeType("m3u", "audio/x-mpegurl");
+
+        assertExtensionFromMimeType("m4a", "audio/mp4");
+        assertMimeTypeFromExtension("audio/mpeg", "m4a");
+
+        assertBidirectional("audio/aac", "aac");
+    }
+
+    @Test public void defaultMap_112162449_video() {
+        assertBidirectional("video/x-flv", "flv");
+        assertBidirectional("video/quicktime", "mov");
+        assertBidirectional("video/mpeg", "mpeg");
+    }
+
+    @Test public void defaultMap_112162449_image() {
+        assertBidirectional("image/heif", "heif");
+        assertBidirectional("image/heif-sequence", "heifs");
+        assertBidirectional("image/heic", "heic");
+        assertBidirectional("image/heic-sequence", "heics");
+        assertMimeTypeFromExtension("image/heif", "hif");
+
+        assertBidirectional("image/x-adobe-dng", "dng");
+        assertBidirectional("image/x-photoshop", "psd");
+
+        assertBidirectional("image/jp2", "jp2");
+        assertMimeTypeFromExtension("image/jp2", "jpg2");
+    }
+
+    @Test public void defaultMap_120135571_audio() {
+        assertMimeTypeFromExtension("audio/mpeg", "m4r");
+    }
+
+    @Test public void defaultMap_136096979_ota() {
+        assertMimeTypeFromExtension("application/vnd.android.ota", "ota");
+    }
+
+    @Test public void defaultMap_wifiConfig_xml() {
+        assertExtensionFromMimeType("xml", "application/x-wifi-config");
+        assertMimeTypeFromExtension("text/xml", "xml");
+    }
+
+    // http://b/122734564
+    @Test public void defaultMap_NonLowercaseMimeType() {
+        // A mixed-case mimeType that appears in mime.types; we expect guessMimeTypeFromExtension()
+        // to return it in lowercase because MimeMap considers lowercase to be the canonical form.
+        String mimeType = "application/vnd.ms-word.document.macroEnabled.12".toLowerCase(Locale.US);
+        assertBidirectional(mimeType, "docm");
+    }
+
+    // Check that the keys given for lookups in either direction are not case sensitive
+    @Test public void defaultMap_CaseInsensitiveKeys() {
+        String mimeType = defaultMimeMap.guessMimeTypeFromExtension("apk");
+        assertNotNull(mimeType);
+
+        assertEquals(mimeType, defaultMimeMap.guessMimeTypeFromExtension("APK"));
+        assertEquals(mimeType, defaultMimeMap.guessMimeTypeFromExtension("aPk"));
+
+        assertEquals("apk", defaultMimeMap.guessExtensionFromMimeType(mimeType));
+        assertEquals("apk", defaultMimeMap.guessExtensionFromMimeType(
+                mimeType.toUpperCase(Locale.US)));
+        assertEquals("apk", defaultMimeMap.guessExtensionFromMimeType(
+                mimeType.toLowerCase(Locale.US)));
+    }
+
+    @Test public void defaultMap_invalid_empty() {
+        checkInvalidExtension("");
+        checkInvalidMimeType("");
+    }
+
+    @Test public void defaultMap_invalid_null() {
+        checkInvalidExtension(null);
+        checkInvalidMimeType(null);
+    }
+
+    @Test public void defaultMap_invalid() {
+        checkInvalidMimeType("invalid mime type");
+        checkInvalidExtension("invalid extension");
+    }
+
+    private void checkInvalidExtension(String s) {
+        assertFalse(defaultMimeMap.hasExtension(s));
+        assertNull(defaultMimeMap.guessMimeTypeFromExtension(s));
+    }
+
+    private void checkInvalidMimeType(String s) {
+        assertFalse(defaultMimeMap.hasMimeType(s));
+        assertNull(defaultMimeMap.guessExtensionFromMimeType(s));
+    }
+
+    private void assertMimeTypeFromExtension(String mimeType, String extension) {
+        final String actual = defaultMimeMap.guessMimeTypeFromExtension(extension);
+        if (!Objects.equals(mimeType, actual)) {
+            fail("Expected " + mimeType + " but was " + actual + " for extension " + extension);
         }
     }
 
+    private void assertExtensionFromMimeType(String extension, String mimeType) {
+        final String actual = defaultMimeMap.guessExtensionFromMimeType(mimeType);
+        if (!Objects.equals(extension, actual)) {
+            fail("Expected " + extension + " but was " + actual + " for type " + mimeType);
+        }
+    }
+
+    private void assertBidirectional(String mimeType, String extension) {
+        assertMimeTypeFromExtension(mimeType, extension);
+        assertExtensionFromMimeType(extension, mimeType);
+    }
 }
diff --git a/mmodules/core_platform_api/api/platform/current-api.txt b/mmodules/core_platform_api/api/platform/current-api.txt
index 8c8c384..1810c8a 100644
--- a/mmodules/core_platform_api/api/platform/current-api.txt
+++ b/mmodules/core_platform_api/api/platform/current-api.txt
@@ -1184,7 +1184,7 @@
   }
 
   public abstract class MimeMap {
-    ctor protected MimeMap();
+    method public static boolean compareAndSetDefault(@NonNull libcore.net.MimeMap, @NonNull libcore.net.MimeMap);
     method @NonNull public static libcore.net.MimeMap getDefault();
     method @Nullable protected abstract String guessExtensionFromLowerCaseMimeType(@NonNull String);
     method @Nullable public final String guessExtensionFromMimeType(@Nullable String);
@@ -1192,9 +1192,6 @@
     method @Nullable protected abstract String guessMimeTypeFromLowerCaseExtension(@NonNull String);
     method public final boolean hasExtension(@Nullable String);
     method public final boolean hasMimeType(@Nullable String);
-    method public static boolean isNullOrEmpty(@Nullable String);
-    method public static void setDefault(@NonNull libcore.net.MimeMap);
-    method @NonNull public static String toLowerCase(@NonNull String);
   }
 
   public abstract class NetworkSecurityPolicy {
diff --git a/non_openjdk_java_files.bp b/non_openjdk_java_files.bp
index e98cd0c..77c56e4 100644
--- a/non_openjdk_java_files.bp
+++ b/non_openjdk_java_files.bp
@@ -180,6 +180,7 @@
         "luni/src/main/java/libcore/io/Streams.java",
         "luni/src/main/java/libcore/net/InetAddressUtils.java",
         "luni/src/main/java/libcore/net/MimeMap.java",
+        "luni/src/main/java/libcore/net/MimeMapImpl.java",
         "luni/src/main/java/libcore/net/NetworkSecurityPolicy.java",
         "luni/src/main/java/libcore/net/event/NetworkEventDispatcher.java",
         "luni/src/main/java/libcore/timezone/CountryTimeZones.java",