diff options
-rw-r--r-- | JavaLibrary.bp | 2 | ||||
-rw-r--r-- | luni/src/main/java/libcore/net/MimeMap.java | 68 | ||||
-rw-r--r-- | luni/src/main/java/libcore/net/MimeMapImpl.java | 175 | ||||
-rw-r--r-- | luni/src/main/java/libcore/net/android.mime.types | 146 | ||||
-rw-r--r-- | luni/src/test/java/libcore/libcore/net/MimeMapTest.java | 233 | ||||
-rw-r--r-- | mmodules/core_platform_api/api/platform/current-api.txt | 5 | ||||
-rw-r--r-- | non_openjdk_java_files.bp | 1 |
7 files changed, 561 insertions, 69 deletions
diff --git a/JavaLibrary.bp b/JavaLibrary.bp index c85cfe549b..8a5813b382 100644 --- a/JavaLibrary.bp +++ b/JavaLibrary.bp @@ -52,6 +52,7 @@ filegroup { ], 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 @@ filegroup { 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 107ccabe0a..afb6cbd25f 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 @@ import libcore.util.Nullable; */ @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 @@ public abstract class MimeMap { /** * 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 0000000000..a10dd6a724 --- /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 0000000000..1ca912e851 --- /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 c4352645f8..de98309cf8 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.After; 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 @@ public class MimeMapTest { } 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 @@ public class MimeMapTest { 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) { + } + + try { + MimeMap.compareAndSetDefault(null, defaultMimeMap); fail(); } catch (NullPointerException expected) { - assertEquals(defaultMimeMap, MimeMap.getDefault()); + } + + // 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 8c8c384779..1810c8adeb 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 @@ package libcore.net { } 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 @@ package libcore.net { 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 e98cd0cc01..77c56e4bb8 100644 --- a/non_openjdk_java_files.bp +++ b/non_openjdk_java_files.bp @@ -180,6 +180,7 @@ filegroup { "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", |