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