1 /*
2  * Copyright (C) 2007 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 android.media;
18 
19 import static android.content.ContentResolver.MIME_TYPE_DEFAULT;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.compat.annotation.UnsupportedAppUsage;
24 import android.mtp.MtpConstants;
25 
26 import libcore.content.type.MimeMap;
27 
28 import java.util.HashMap;
29 import java.util.Locale;
30 
31 /**
32  * MediaScanner helper class.
33  * <p>
34  * This heavily relies upon extension to MIME type mappings which are maintained
35  * in {@link MimeMap}, to ensure consistency across the OS.
36  * <p>
37  * When adding a new file type, first add the MIME type mapping to
38  * {@link MimeMap}, and then add the MTP format mapping here.
39  *
40  * @hide
41  */
42 public class MediaFile {
43 
44     /** @deprecated file types no longer exist */
45     @Deprecated
46     @UnsupportedAppUsage
47     private static final int FIRST_AUDIO_FILE_TYPE = 1;
48     /** @deprecated file types no longer exist */
49     @Deprecated
50     @UnsupportedAppUsage
51     private static final int LAST_AUDIO_FILE_TYPE = 10;
52 
53     /** @deprecated file types no longer exist */
54     @Deprecated
55     public static class MediaFileType {
56         @UnsupportedAppUsage
57         public final int fileType;
58         @UnsupportedAppUsage
59         public final String mimeType;
60 
MediaFileType(int fileType, String mimeType)61         MediaFileType(int fileType, String mimeType) {
62             this.fileType = fileType;
63             this.mimeType = mimeType;
64         }
65     }
66 
67     /** @deprecated file types no longer exist */
68     @Deprecated
69     @UnsupportedAppUsage
70     private static final HashMap<String, MediaFileType> sFileTypeMap = new HashMap<>();
71     /** @deprecated file types no longer exist */
72     @Deprecated
73     @UnsupportedAppUsage
74     private static final HashMap<String, Integer> sFileTypeToFormatMap = new HashMap<>();
75 
76     // maps mime type to MTP format code
77     @UnsupportedAppUsage
78     private static final HashMap<String, Integer> sMimeTypeToFormatMap = new HashMap<>();
79     // maps MTP format code to mime type
80     @UnsupportedAppUsage
81     private static final HashMap<Integer, String> sFormatToMimeTypeMap = new HashMap<>();
82 
83     @UnsupportedAppUsage
MediaFile()84     public MediaFile() {
85     }
86 
87     /** @deprecated file types no longer exist */
88     @Deprecated
89     @UnsupportedAppUsage
addFileType(String extension, int fileType, String mimeType)90     static void addFileType(String extension, int fileType, String mimeType) {
91     }
92 
addFileType(int mtpFormatCode, @NonNull String mimeType)93     private static void addFileType(int mtpFormatCode, @NonNull String mimeType) {
94         if (!sMimeTypeToFormatMap.containsKey(mimeType)) {
95             sMimeTypeToFormatMap.put(mimeType, Integer.valueOf(mtpFormatCode));
96         }
97         if (!sFormatToMimeTypeMap.containsKey(mtpFormatCode)) {
98             sFormatToMimeTypeMap.put(mtpFormatCode, mimeType);
99         }
100     }
101 
102     static {
addFileType(MtpConstants.FORMAT_MP3, "audio/mpeg")103         addFileType(MtpConstants.FORMAT_MP3, "audio/mpeg");
addFileType(MtpConstants.FORMAT_WAV, "audio/x-wav")104         addFileType(MtpConstants.FORMAT_WAV, "audio/x-wav");
addFileType(MtpConstants.FORMAT_WMA, "audio/x-ms-wma")105         addFileType(MtpConstants.FORMAT_WMA, "audio/x-ms-wma");
addFileType(MtpConstants.FORMAT_OGG, "audio/ogg")106         addFileType(MtpConstants.FORMAT_OGG, "audio/ogg");
addFileType(MtpConstants.FORMAT_AAC, "audio/aac")107         addFileType(MtpConstants.FORMAT_AAC, "audio/aac");
addFileType(MtpConstants.FORMAT_FLAC, "audio/flac")108         addFileType(MtpConstants.FORMAT_FLAC, "audio/flac");
addFileType(MtpConstants.FORMAT_AIFF, "audio/x-aiff")109         addFileType(MtpConstants.FORMAT_AIFF, "audio/x-aiff");
addFileType(MtpConstants.FORMAT_MP2, "audio/mpeg")110         addFileType(MtpConstants.FORMAT_MP2, "audio/mpeg");
111 
addFileType(MtpConstants.FORMAT_MPEG, "video/mpeg")112         addFileType(MtpConstants.FORMAT_MPEG, "video/mpeg");
addFileType(MtpConstants.FORMAT_MP4_CONTAINER, "video/mp4")113         addFileType(MtpConstants.FORMAT_MP4_CONTAINER, "video/mp4");
addFileType(MtpConstants.FORMAT_3GP_CONTAINER, "video/3gpp")114         addFileType(MtpConstants.FORMAT_3GP_CONTAINER, "video/3gpp");
addFileType(MtpConstants.FORMAT_3GP_CONTAINER, "video/3gpp2")115         addFileType(MtpConstants.FORMAT_3GP_CONTAINER, "video/3gpp2");
addFileType(MtpConstants.FORMAT_AVI, "video/avi")116         addFileType(MtpConstants.FORMAT_AVI, "video/avi");
addFileType(MtpConstants.FORMAT_WMV, "video/x-ms-wmv")117         addFileType(MtpConstants.FORMAT_WMV, "video/x-ms-wmv");
addFileType(MtpConstants.FORMAT_ASF, "video/x-ms-asf")118         addFileType(MtpConstants.FORMAT_ASF, "video/x-ms-asf");
119 
addFileType(MtpConstants.FORMAT_EXIF_JPEG, "image/jpeg")120         addFileType(MtpConstants.FORMAT_EXIF_JPEG, "image/jpeg");
addFileType(MtpConstants.FORMAT_GIF, "image/gif")121         addFileType(MtpConstants.FORMAT_GIF, "image/gif");
addFileType(MtpConstants.FORMAT_PNG, "image/png")122         addFileType(MtpConstants.FORMAT_PNG, "image/png");
addFileType(MtpConstants.FORMAT_BMP, "image/x-ms-bmp")123         addFileType(MtpConstants.FORMAT_BMP, "image/x-ms-bmp");
addFileType(MtpConstants.FORMAT_HEIF, "image/heif")124         addFileType(MtpConstants.FORMAT_HEIF, "image/heif");
addFileType(MtpConstants.FORMAT_DNG, "image/x-adobe-dng")125         addFileType(MtpConstants.FORMAT_DNG, "image/x-adobe-dng");
addFileType(MtpConstants.FORMAT_TIFF, "image/tiff")126         addFileType(MtpConstants.FORMAT_TIFF, "image/tiff");
addFileType(MtpConstants.FORMAT_TIFF, "image/x-canon-cr2")127         addFileType(MtpConstants.FORMAT_TIFF, "image/x-canon-cr2");
addFileType(MtpConstants.FORMAT_TIFF, "image/x-nikon-nrw")128         addFileType(MtpConstants.FORMAT_TIFF, "image/x-nikon-nrw");
addFileType(MtpConstants.FORMAT_TIFF, "image/x-sony-arw")129         addFileType(MtpConstants.FORMAT_TIFF, "image/x-sony-arw");
addFileType(MtpConstants.FORMAT_TIFF, "image/x-panasonic-rw2")130         addFileType(MtpConstants.FORMAT_TIFF, "image/x-panasonic-rw2");
addFileType(MtpConstants.FORMAT_TIFF, "image/x-olympus-orf")131         addFileType(MtpConstants.FORMAT_TIFF, "image/x-olympus-orf");
addFileType(MtpConstants.FORMAT_TIFF, "image/x-pentax-pef")132         addFileType(MtpConstants.FORMAT_TIFF, "image/x-pentax-pef");
addFileType(MtpConstants.FORMAT_TIFF, "image/x-samsung-srw")133         addFileType(MtpConstants.FORMAT_TIFF, "image/x-samsung-srw");
addFileType(MtpConstants.FORMAT_TIFF_EP, "image/tiff")134         addFileType(MtpConstants.FORMAT_TIFF_EP, "image/tiff");
addFileType(MtpConstants.FORMAT_TIFF_EP, "image/x-nikon-nef")135         addFileType(MtpConstants.FORMAT_TIFF_EP, "image/x-nikon-nef");
addFileType(MtpConstants.FORMAT_JP2, "image/jp2")136         addFileType(MtpConstants.FORMAT_JP2, "image/jp2");
addFileType(MtpConstants.FORMAT_JPX, "image/jpx")137         addFileType(MtpConstants.FORMAT_JPX, "image/jpx");
138 
addFileType(MtpConstants.FORMAT_M3U_PLAYLIST, "audio/x-mpegurl")139         addFileType(MtpConstants.FORMAT_M3U_PLAYLIST, "audio/x-mpegurl");
addFileType(MtpConstants.FORMAT_PLS_PLAYLIST, "audio/x-scpls")140         addFileType(MtpConstants.FORMAT_PLS_PLAYLIST, "audio/x-scpls");
addFileType(MtpConstants.FORMAT_WPL_PLAYLIST, "application/vnd.ms-wpl")141         addFileType(MtpConstants.FORMAT_WPL_PLAYLIST, "application/vnd.ms-wpl");
addFileType(MtpConstants.FORMAT_ASX_PLAYLIST, "video/x-ms-asf")142         addFileType(MtpConstants.FORMAT_ASX_PLAYLIST, "video/x-ms-asf");
143 
addFileType(MtpConstants.FORMAT_TEXT, "text/plain")144         addFileType(MtpConstants.FORMAT_TEXT, "text/plain");
addFileType(MtpConstants.FORMAT_HTML, "text/html")145         addFileType(MtpConstants.FORMAT_HTML, "text/html");
addFileType(MtpConstants.FORMAT_XML_DOCUMENT, "text/xml")146         addFileType(MtpConstants.FORMAT_XML_DOCUMENT, "text/xml");
147 
addFileType(MtpConstants.FORMAT_MS_WORD_DOCUMENT, "application/msword")148         addFileType(MtpConstants.FORMAT_MS_WORD_DOCUMENT,
149                 "application/msword");
addFileType(MtpConstants.FORMAT_MS_WORD_DOCUMENT, "application/vnd.openxmlformats-officedocument.wordprocessingml.document")150         addFileType(MtpConstants.FORMAT_MS_WORD_DOCUMENT,
151                 "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
addFileType(MtpConstants.FORMAT_MS_EXCEL_SPREADSHEET, "application/vnd.ms-excel")152         addFileType(MtpConstants.FORMAT_MS_EXCEL_SPREADSHEET,
153                 "application/vnd.ms-excel");
addFileType(MtpConstants.FORMAT_MS_EXCEL_SPREADSHEET, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")154         addFileType(MtpConstants.FORMAT_MS_EXCEL_SPREADSHEET,
155                 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
addFileType(MtpConstants.FORMAT_MS_POWERPOINT_PRESENTATION, "application/vnd.ms-powerpoint")156         addFileType(MtpConstants.FORMAT_MS_POWERPOINT_PRESENTATION,
157                 "application/vnd.ms-powerpoint");
addFileType(MtpConstants.FORMAT_MS_POWERPOINT_PRESENTATION, "application/vnd.openxmlformats-officedocument.presentationml.presentation")158         addFileType(MtpConstants.FORMAT_MS_POWERPOINT_PRESENTATION,
159                 "application/vnd.openxmlformats-officedocument.presentationml.presentation");
160     }
161 
162     /** @deprecated file types no longer exist */
163     @Deprecated
164     @UnsupportedAppUsage
isAudioFileType(int fileType)165     public static boolean isAudioFileType(int fileType) {
166         return false;
167     }
168 
169     /** @deprecated file types no longer exist */
170     @Deprecated
171     @UnsupportedAppUsage
isVideoFileType(int fileType)172     public static boolean isVideoFileType(int fileType) {
173         return false;
174     }
175 
176     /** @deprecated file types no longer exist */
177     @Deprecated
178     @UnsupportedAppUsage
isImageFileType(int fileType)179     public static boolean isImageFileType(int fileType) {
180         return false;
181     }
182 
183     /** @deprecated file types no longer exist */
184     @Deprecated
185     @UnsupportedAppUsage
isPlayListFileType(int fileType)186     public static boolean isPlayListFileType(int fileType) {
187         return false;
188     }
189 
190     /** @deprecated file types no longer exist */
191     @Deprecated
192     @UnsupportedAppUsage
isDrmFileType(int fileType)193     public static boolean isDrmFileType(int fileType) {
194         return false;
195     }
196 
197     /** @deprecated file types no longer exist */
198     @Deprecated
199     @UnsupportedAppUsage
getFileType(String path)200     public static MediaFileType getFileType(String path) {
201         return null;
202     }
203 
204     /**
205      * Check whether the mime type is document or not.
206      * @param mimeType the mime type to check
207      * @return true, if the mimeType is matched. Otherwise, false.
208      */
isDocumentMimeType(@ullable String mimeType)209     public static boolean isDocumentMimeType(@Nullable String mimeType) {
210         if (mimeType == null) {
211             return false;
212         }
213 
214         final String normalizedMimeType = normalizeMimeType(mimeType);
215         if (normalizedMimeType.startsWith("text/")) {
216             return true;
217         }
218 
219         switch (normalizedMimeType.toLowerCase(Locale.ROOT)) {
220             case "application/epub+zip":
221             case "application/msword":
222             case "application/pdf":
223             case "application/rtf":
224             case "application/vnd.ms-excel":
225             case "application/vnd.ms-excel.addin.macroenabled.12":
226             case "application/vnd.ms-excel.sheet.binary.macroenabled.12":
227             case "application/vnd.ms-excel.sheet.macroenabled.12":
228             case "application/vnd.ms-excel.template.macroenabled.12":
229             case "application/vnd.ms-powerpoint":
230             case "application/vnd.ms-powerpoint.addin.macroenabled.12":
231             case "application/vnd.ms-powerpoint.presentation.macroenabled.12":
232             case "application/vnd.ms-powerpoint.slideshow.macroenabled.12":
233             case "application/vnd.ms-powerpoint.template.macroenabled.12":
234             case "application/vnd.ms-word.document.macroenabled.12":
235             case "application/vnd.ms-word.template.macroenabled.12":
236             case "application/vnd.oasis.opendocument.chart":
237             case "application/vnd.oasis.opendocument.database":
238             case "application/vnd.oasis.opendocument.formula":
239             case "application/vnd.oasis.opendocument.graphics":
240             case "application/vnd.oasis.opendocument.graphics-template":
241             case "application/vnd.oasis.opendocument.presentation":
242             case "application/vnd.oasis.opendocument.presentation-template":
243             case "application/vnd.oasis.opendocument.spreadsheet":
244             case "application/vnd.oasis.opendocument.spreadsheet-template":
245             case "application/vnd.oasis.opendocument.text":
246             case "application/vnd.oasis.opendocument.text-master":
247             case "application/vnd.oasis.opendocument.text-template":
248             case "application/vnd.oasis.opendocument.text-web":
249             case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
250             case "application/vnd.openxmlformats-officedocument.presentationml.slideshow":
251             case "application/vnd.openxmlformats-officedocument.presentationml.template":
252             case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
253             case "application/vnd.openxmlformats-officedocument.spreadsheetml.template":
254             case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
255             case "application/vnd.openxmlformats-officedocument.wordprocessingml.template":
256             case "application/vnd.stardivision.calc":
257             case "application/vnd.stardivision.chart":
258             case "application/vnd.stardivision.draw":
259             case "application/vnd.stardivision.impress":
260             case "application/vnd.stardivision.impress-packed":
261             case "application/vnd.stardivision.mail":
262             case "application/vnd.stardivision.math":
263             case "application/vnd.stardivision.writer":
264             case "application/vnd.stardivision.writer-global":
265             case "application/vnd.sun.xml.calc":
266             case "application/vnd.sun.xml.calc.template":
267             case "application/vnd.sun.xml.draw":
268             case "application/vnd.sun.xml.draw.template":
269             case "application/vnd.sun.xml.impress":
270             case "application/vnd.sun.xml.impress.template":
271             case "application/vnd.sun.xml.math":
272             case "application/vnd.sun.xml.writer":
273             case "application/vnd.sun.xml.writer.global":
274             case "application/vnd.sun.xml.writer.template":
275             case "application/x-mspublisher":
276                 return true;
277             default:
278                 return false;
279         }
280     }
281 
isExifMimeType(@ullable String mimeType)282     public static boolean isExifMimeType(@Nullable String mimeType) {
283         // For simplicity, assume that all image files might have EXIF data
284         return isImageMimeType(mimeType);
285     }
286 
isAudioMimeType(@ullable String mimeType)287     public static boolean isAudioMimeType(@Nullable String mimeType) {
288         return normalizeMimeType(mimeType).startsWith("audio/");
289     }
290 
isVideoMimeType(@ullable String mimeType)291     public static boolean isVideoMimeType(@Nullable String mimeType) {
292         return normalizeMimeType(mimeType).startsWith("video/");
293     }
294 
isImageMimeType(@ullable String mimeType)295     public static boolean isImageMimeType(@Nullable String mimeType) {
296         return normalizeMimeType(mimeType).startsWith("image/");
297     }
298 
isPlayListMimeType(@ullable String mimeType)299     public static boolean isPlayListMimeType(@Nullable String mimeType) {
300         switch (normalizeMimeType(mimeType)) {
301             case "application/vnd.ms-wpl":
302             case "audio/x-mpegurl":
303             case "audio/mpegurl":
304             case "application/x-mpegurl":
305             case "application/vnd.apple.mpegurl":
306             case "audio/x-scpls":
307                 return true;
308             default:
309                 return false;
310         }
311     }
312 
isDrmMimeType(@ullable String mimeType)313     public static boolean isDrmMimeType(@Nullable String mimeType) {
314         return normalizeMimeType(mimeType).equals("application/x-android-drm-fl");
315     }
316 
317     // generates a title based on file name
318     @UnsupportedAppUsage
getFileTitle(@onNull String path)319     public static @NonNull String getFileTitle(@NonNull String path) {
320         // extract file name after last slash
321         int lastSlash = path.lastIndexOf('/');
322         if (lastSlash >= 0) {
323             lastSlash++;
324             if (lastSlash < path.length()) {
325                 path = path.substring(lastSlash);
326             }
327         }
328         // truncate the file extension (if any)
329         int lastDot = path.lastIndexOf('.');
330         if (lastDot > 0) {
331             path = path.substring(0, lastDot);
332         }
333         return path;
334     }
335 
getFileExtension(@ullable String path)336     public static @Nullable String getFileExtension(@Nullable String path) {
337         if (path == null) {
338             return null;
339         }
340         int lastDot = path.lastIndexOf('.');
341         if (lastDot >= 0) {
342             return path.substring(lastDot + 1);
343         } else {
344             return null;
345         }
346     }
347 
348     /** @deprecated file types no longer exist */
349     @Deprecated
350     @UnsupportedAppUsage
getFileTypeForMimeType(String mimeType)351     public static int getFileTypeForMimeType(String mimeType) {
352         return 0;
353     }
354 
355     /**
356      * Find the best MIME type for the given item. Prefers mappings from file
357      * extensions, since they're more accurate than format codes.
358      */
getMimeType(@ullable String path, int formatCode)359     public static @NonNull String getMimeType(@Nullable String path, int formatCode) {
360         // First look for extension mapping
361         String mimeType = getMimeTypeForFile(path);
362         if (!MIME_TYPE_DEFAULT.equals(mimeType)) {
363             return mimeType;
364         }
365 
366         // Otherwise look for format mapping
367         return getMimeTypeForFormatCode(formatCode);
368     }
369 
370     @UnsupportedAppUsage
getMimeTypeForFile(@ullable String path)371     public static @NonNull String getMimeTypeForFile(@Nullable String path) {
372         String ext = getFileExtension(path);
373         final String mimeType = MimeMap.getDefault().guessMimeTypeFromExtension(ext);
374         return (mimeType != null) ? mimeType : MIME_TYPE_DEFAULT;
375     }
376 
getMimeTypeForFormatCode(int formatCode)377     public static @NonNull String getMimeTypeForFormatCode(int formatCode) {
378         final String mimeType = sFormatToMimeTypeMap.get(formatCode);
379         return (mimeType != null) ? mimeType : MIME_TYPE_DEFAULT;
380     }
381 
382     /**
383      * Find the best MTP format code mapping for the given item. Prefers
384      * mappings from MIME types, since they're more accurate than file
385      * extensions.
386      */
getFormatCode(@ullable String path, @Nullable String mimeType)387     public static int getFormatCode(@Nullable String path, @Nullable String mimeType) {
388         // First look for MIME type mapping
389         int formatCode = getFormatCodeForMimeType(mimeType);
390         if (formatCode != MtpConstants.FORMAT_UNDEFINED) {
391             return formatCode;
392         }
393 
394         // Otherwise look for extension mapping
395         return getFormatCodeForFile(path);
396     }
397 
getFormatCodeForFile(@ullable String path)398     public static int getFormatCodeForFile(@Nullable String path) {
399         return getFormatCodeForMimeType(getMimeTypeForFile(path));
400     }
401 
getFormatCodeForMimeType(@ullable String mimeType)402     public static int getFormatCodeForMimeType(@Nullable String mimeType) {
403         if (mimeType == null) {
404             return MtpConstants.FORMAT_UNDEFINED;
405         }
406 
407         // First look for direct mapping
408         Integer value = sMimeTypeToFormatMap.get(mimeType);
409         if (value != null) {
410             return value.intValue();
411         }
412 
413         // Otherwise look for indirect mapping
414         mimeType = normalizeMimeType(mimeType);
415         value = sMimeTypeToFormatMap.get(mimeType);
416         if (value != null) {
417             return value.intValue();
418         } else if (mimeType.startsWith("audio/")) {
419             return MtpConstants.FORMAT_UNDEFINED_AUDIO;
420         } else if (mimeType.startsWith("video/")) {
421             return MtpConstants.FORMAT_UNDEFINED_VIDEO;
422         } else if (mimeType.startsWith("image/")) {
423             return MtpConstants.FORMAT_DEFINED;
424         } else {
425             return MtpConstants.FORMAT_UNDEFINED;
426         }
427     }
428 
429     /**
430      * Normalize the given MIME type by bouncing through a default file
431      * extension, if defined. This handles cases like "application/x-flac" to
432      * ".flac" to "audio/flac".
433      */
normalizeMimeType(@ullable String mimeType)434     private static @NonNull String normalizeMimeType(@Nullable String mimeType) {
435         MimeMap mimeMap = MimeMap.getDefault();
436         final String extension = mimeMap.guessExtensionFromMimeType(mimeType);
437         if (extension != null) {
438             final String extensionMimeType = mimeMap.guessMimeTypeFromExtension(extension);
439             if (extensionMimeType != null) {
440                 return extensionMimeType;
441             }
442         }
443         return (mimeType != null) ? mimeType : MIME_TYPE_DEFAULT;
444     }
445 }
446