1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.providers.media.util; 18 19 import android.content.ClipDescription; 20 import android.mtp.MtpConstants; 21 import android.provider.MediaStore.Files.FileColumns; 22 import android.webkit.MimeTypeMap; 23 24 import androidx.annotation.NonNull; 25 import androidx.annotation.Nullable; 26 27 import java.io.File; 28 import java.util.Locale; 29 import java.util.Objects; 30 31 public class MimeUtils { 32 /** 33 * Variant of {@link Objects#equal(Object, Object)} but which tests with 34 * case-insensitivity. 35 */ equalIgnoreCase(@ullable String a, @Nullable String b)36 public static boolean equalIgnoreCase(@Nullable String a, @Nullable String b) { 37 return (a != null) && a.equalsIgnoreCase(b); 38 } 39 40 /** 41 * Variant of {@link String#startsWith(String)} but which tests with 42 * case-insensitivity. 43 */ startsWithIgnoreCase(@ullable String target, @Nullable String other)44 public static boolean startsWithIgnoreCase(@Nullable String target, @Nullable String other) { 45 if (target == null || other == null) return false; 46 if (other.length() > target.length()) return false; 47 return target.regionMatches(true, 0, other, 0, other.length()); 48 } 49 50 /** 51 * Resolve the MIME type of the given file, returning 52 * {@code application/octet-stream} if the type cannot be determined. 53 */ resolveMimeType(@onNull File file)54 public static @NonNull String resolveMimeType(@NonNull File file) { 55 final String extension = FileUtils.extractFileExtension(file.getPath()); 56 if (extension == null) return ClipDescription.MIMETYPE_UNKNOWN; 57 58 final String mimeType = MimeTypeMap.getSingleton() 59 .getMimeTypeFromExtension(extension.toLowerCase(Locale.ROOT)); 60 if (mimeType == null) return ClipDescription.MIMETYPE_UNKNOWN; 61 62 return mimeType; 63 } 64 65 /** 66 * Resolve the {@link FileColumns#MEDIA_TYPE} of the given MIME type. This 67 * carefully checks for more specific types before generic ones, such as 68 * treating {@code audio/mpegurl} as a playlist instead of an audio file. 69 */ resolveMediaType(@onNull String mimeType)70 public static int resolveMediaType(@NonNull String mimeType) { 71 if (isPlaylistMimeType(mimeType)) { 72 return FileColumns.MEDIA_TYPE_PLAYLIST; 73 } else if (isSubtitleMimeType(mimeType)) { 74 return FileColumns.MEDIA_TYPE_SUBTITLE; 75 } else if (isAudioMimeType(mimeType)) { 76 return FileColumns.MEDIA_TYPE_AUDIO; 77 } else if (isVideoMimeType(mimeType)) { 78 return FileColumns.MEDIA_TYPE_VIDEO; 79 } else if (isImageMimeType(mimeType)) { 80 return FileColumns.MEDIA_TYPE_IMAGE; 81 } else if (isDocumentMimeType(mimeType)) { 82 return FileColumns.MEDIA_TYPE_DOCUMENT; 83 } else { 84 return FileColumns.MEDIA_TYPE_NONE; 85 } 86 } 87 88 /** 89 * Resolve the {@link FileColumns#FORMAT} of the given MIME type. Note that 90 * since this column isn't public API, we're okay only getting very rough 91 * values in place, and it's not worthwhile to build out complex matching. 92 */ resolveFormatCode(@ullable String mimeType)93 public static int resolveFormatCode(@Nullable String mimeType) { 94 final int mediaType = resolveMediaType(mimeType); 95 switch (mediaType) { 96 case FileColumns.MEDIA_TYPE_AUDIO: 97 return MtpConstants.FORMAT_UNDEFINED_AUDIO; 98 case FileColumns.MEDIA_TYPE_VIDEO: 99 return MtpConstants.FORMAT_UNDEFINED_VIDEO; 100 case FileColumns.MEDIA_TYPE_IMAGE: 101 return MtpConstants.FORMAT_DEFINED; 102 default: 103 return MtpConstants.FORMAT_UNDEFINED; 104 } 105 } 106 extractPrimaryType(@onNull String mimeType)107 public static @NonNull String extractPrimaryType(@NonNull String mimeType) { 108 final int slash = mimeType.indexOf('/'); 109 if (slash == -1) { 110 throw new IllegalArgumentException(); 111 } 112 return mimeType.substring(0, slash); 113 } 114 isAudioMimeType(@ullable String mimeType)115 public static boolean isAudioMimeType(@Nullable String mimeType) { 116 if (mimeType == null) return false; 117 return startsWithIgnoreCase(mimeType, "audio/"); 118 } 119 isVideoMimeType(@ullable String mimeType)120 public static boolean isVideoMimeType(@Nullable String mimeType) { 121 if (mimeType == null) return false; 122 return startsWithIgnoreCase(mimeType, "video/"); 123 } 124 isImageMimeType(@ullable String mimeType)125 public static boolean isImageMimeType(@Nullable String mimeType) { 126 if (mimeType == null) return false; 127 return startsWithIgnoreCase(mimeType, "image/"); 128 } 129 isPlaylistMimeType(@ullable String mimeType)130 public static boolean isPlaylistMimeType(@Nullable String mimeType) { 131 if (mimeType == null) return false; 132 switch (mimeType.toLowerCase(Locale.ROOT)) { 133 case "application/vnd.apple.mpegurl": 134 case "application/vnd.ms-wpl": 135 case "application/x-extension-smpl": 136 case "application/x-mpegurl": 137 case "application/xspf+xml": 138 case "audio/mpegurl": 139 case "audio/x-mpegurl": 140 case "audio/x-scpls": 141 return true; 142 default: 143 return false; 144 } 145 } 146 isSubtitleMimeType(@ullable String mimeType)147 public static boolean isSubtitleMimeType(@Nullable String mimeType) { 148 if (mimeType == null) return false; 149 switch (mimeType.toLowerCase(Locale.ROOT)) { 150 case "application/lrc": 151 case "application/smil+xml": 152 case "application/ttml+xml": 153 case "application/x-extension-cap": 154 case "application/x-extension-srt": 155 case "application/x-extension-sub": 156 case "application/x-extension-vtt": 157 case "application/x-subrip": 158 case "text/vtt": 159 return true; 160 default: 161 return false; 162 } 163 } 164 isDocumentMimeType(@ullable String mimeType)165 public static boolean isDocumentMimeType(@Nullable String mimeType) { 166 if (mimeType == null) return false; 167 168 if (startsWithIgnoreCase(mimeType, "text/")) return true; 169 170 switch (mimeType.toLowerCase(Locale.ROOT)) { 171 case "application/epub+zip": 172 case "application/msword": 173 case "application/pdf": 174 case "application/rtf": 175 case "application/vnd.ms-excel": 176 case "application/vnd.ms-excel.addin.macroenabled.12": 177 case "application/vnd.ms-excel.sheet.binary.macroenabled.12": 178 case "application/vnd.ms-excel.sheet.macroenabled.12": 179 case "application/vnd.ms-excel.template.macroenabled.12": 180 case "application/vnd.ms-powerpoint": 181 case "application/vnd.ms-powerpoint.addin.macroenabled.12": 182 case "application/vnd.ms-powerpoint.presentation.macroenabled.12": 183 case "application/vnd.ms-powerpoint.slideshow.macroenabled.12": 184 case "application/vnd.ms-powerpoint.template.macroenabled.12": 185 case "application/vnd.ms-word.document.macroenabled.12": 186 case "application/vnd.ms-word.template.macroenabled.12": 187 case "application/vnd.oasis.opendocument.chart": 188 case "application/vnd.oasis.opendocument.database": 189 case "application/vnd.oasis.opendocument.formula": 190 case "application/vnd.oasis.opendocument.graphics": 191 case "application/vnd.oasis.opendocument.graphics-template": 192 case "application/vnd.oasis.opendocument.presentation": 193 case "application/vnd.oasis.opendocument.presentation-template": 194 case "application/vnd.oasis.opendocument.spreadsheet": 195 case "application/vnd.oasis.opendocument.spreadsheet-template": 196 case "application/vnd.oasis.opendocument.text": 197 case "application/vnd.oasis.opendocument.text-master": 198 case "application/vnd.oasis.opendocument.text-template": 199 case "application/vnd.oasis.opendocument.text-web": 200 case "application/vnd.openxmlformats-officedocument.presentationml.presentation": 201 case "application/vnd.openxmlformats-officedocument.presentationml.slideshow": 202 case "application/vnd.openxmlformats-officedocument.presentationml.template": 203 case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": 204 case "application/vnd.openxmlformats-officedocument.spreadsheetml.template": 205 case "application/vnd.openxmlformats-officedocument.wordprocessingml.document": 206 case "application/vnd.openxmlformats-officedocument.wordprocessingml.template": 207 case "application/vnd.stardivision.calc": 208 case "application/vnd.stardivision.chart": 209 case "application/vnd.stardivision.draw": 210 case "application/vnd.stardivision.impress": 211 case "application/vnd.stardivision.impress-packed": 212 case "application/vnd.stardivision.mail": 213 case "application/vnd.stardivision.math": 214 case "application/vnd.stardivision.writer": 215 case "application/vnd.stardivision.writer-global": 216 case "application/vnd.sun.xml.calc": 217 case "application/vnd.sun.xml.calc.template": 218 case "application/vnd.sun.xml.draw": 219 case "application/vnd.sun.xml.draw.template": 220 case "application/vnd.sun.xml.impress": 221 case "application/vnd.sun.xml.impress.template": 222 case "application/vnd.sun.xml.math": 223 case "application/vnd.sun.xml.writer": 224 case "application/vnd.sun.xml.writer.global": 225 case "application/vnd.sun.xml.writer.template": 226 case "application/x-mspublisher": 227 return true; 228 default: 229 return false; 230 } 231 } 232 } 233