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