2 * Copyright (C) 2012 The CyanogenMod Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.cyanogenmod.filemanager.util;
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.text.TextUtils;
22 import android.util.Log;
24 import com.cyanogenmod.filemanager.R;
25 import com.cyanogenmod.filemanager.console.secure.SecureConsole;
26 import com.cyanogenmod.filemanager.model.BlockDevice;
27 import com.cyanogenmod.filemanager.model.CharacterDevice;
28 import com.cyanogenmod.filemanager.model.Directory;
29 import com.cyanogenmod.filemanager.model.DomainSocket;
30 import com.cyanogenmod.filemanager.model.FileSystemObject;
31 import com.cyanogenmod.filemanager.model.NamedPipe;
32 import com.cyanogenmod.filemanager.model.Symlink;
33 import com.cyanogenmod.filemanager.model.SystemFile;
36 import java.util.ArrayList;
37 import java.util.Enumeration;
38 import java.util.HashMap;
39 import java.util.Locale;
41 import java.util.Properties;
44 * A helper class with useful methods for deal with mime types.
46 public final class MimeTypeHelper {
49 * Enumeration of mime/type' categories
51 public enum MimeTypeCategory {
61 * Application, Installer, ...
73 * Document file (text, spreedsheet, presentation, pdf, ...)
81 * Mail file (email, message, contact, calendar, ...)
113 * Security file (certificate, keys, ...)
117 public static String[] names() {
118 MimeTypeCategory[] categories = values();
119 String[] names = new String[categories.length];
121 for (int i = 0; i < categories.length; i++) {
122 names[i] = categories[i].name();
128 public static String[] getFriendlyLocalizedNames(Context context) {
129 MimeTypeCategory[] categories = values();
130 String[] localizedNames = new String[categories.length];
132 for (int i = 0; i < categories.length; i++) {
133 String description = getCategoryDescription(context, categories[i]);
134 if (TextUtils.equals("-", description)) {
135 description = context.getString(R.string.category_all);
137 description = description.substring(0, 1).toUpperCase()
138 + description.substring(1).toLowerCase();
139 localizedNames[i] = description;
142 return localizedNames;
147 * An internal class for holding the mime/type database structure
149 private static class MimeTypeInfo {
150 MimeTypeInfo() {/**NON BLOCK**/}
151 public MimeTypeCategory mCategory;
152 public String mMimeType;
153 public String mDrawable;
159 public int hashCode() {
160 final int prime = 31;
162 result = prime * result
163 + ((this.mCategory == null) ? 0 : this.mCategory.hashCode());
164 result = prime * result
165 + ((this.mDrawable == null) ? 0 : this.mDrawable.hashCode());
166 result = prime * result
167 + ((this.mMimeType == null) ? 0 : this.mMimeType.hashCode());
175 public boolean equals(Object obj) {
180 if (getClass() != obj.getClass())
182 MimeTypeInfo other = (MimeTypeInfo) obj;
183 if (this.mCategory != other.mCategory)
185 if (this.mDrawable == null) {
186 if (other.mDrawable != null)
188 } else if (!this.mDrawable.equals(other.mDrawable))
190 if (this.mMimeType == null) {
191 if (other.mMimeType != null)
193 } else if (!this.mMimeType.equals(other.mMimeType))
202 public String toString() {
203 return "MimeTypeInfo [mCategory=" + this.mCategory + //$NON-NLS-1$
204 ", mMimeType="+ this.mMimeType + //$NON-NLS-1$
205 ", mDrawable=" + this.mDrawable + "]"; //$NON-NLS-1$ //$NON-NLS-2$
209 private static final String TAG = "MimeTypeHelper"; //$NON-NLS-1$
212 * A constant that defines a string of all mime-types
214 public static final String ALL_MIME_TYPES = "*/*"; //$NON-NLS-1$
216 private static Map<String, ArrayList<MimeTypeInfo>> sMimeTypes;
218 * Maps from a combination key of <extension> + <mimetype> to MimeTypeInfo objects.
220 private static HashMap<String, MimeTypeInfo> sExtensionMimeTypes;
223 * Constructor of <code>MimeTypeHelper</code>.
225 private MimeTypeHelper() {
230 * Method that checks whether a certain mime type is known to
233 * @param context The current context
234 * @param mimeType The mime type to be checked
235 * @return true if mime type is known, false otherwise
237 public static final boolean isMimeTypeKnown(Context context, String mimeType) {
238 //Ensure that mime types are loaded
239 if (sMimeTypes == null) {
240 loadMimeTypes(context);
243 if (mimeType == null) {
247 for (ArrayList<MimeTypeInfo> mimeTypeInfoList : sMimeTypes.values()) {
248 for (MimeTypeInfo info : mimeTypeInfoList) {
249 String mimeTypeRegExp = convertToRegExp(mimeType);
250 if (info.mMimeType.matches(mimeTypeRegExp)) {
260 * Method that returns the associated mime/type icon resource identifier of
261 * the {@link FileSystemObject}.
263 * @param context The current context
264 * @param fso The file system object
265 * @return String The associated mime/type icon resource identifier
267 public static final String getIcon(Context context, FileSystemObject fso) {
268 return getIcon(context, fso, false);
271 public static final String getIcon(Context context, FileSystemObject fso, boolean firstFound) {
272 //Ensure that mime types are loaded
273 if (sMimeTypes == null) {
274 loadMimeTypes(context);
277 // Return the symlink ref mime/type icon
278 if (fso instanceof Symlink && ((Symlink) fso).getLinkRef() != null) {
279 return getIcon(context, ((Symlink) fso).getLinkRef());
282 //Check if the argument is a folder
283 if (fso instanceof Directory) {
284 if (fso.isSecure() && SecureConsole.isSecureStorageDir(fso.getFullPath())) {
285 return "fso_folder_secure"; //$NON-NLS-1$
286 } else if (fso.isRemote()) {
287 return "fso_folder_remote"; //$NON-NLS-1$
289 return "ic_fso_folder_drawable"; //$NON-NLS-1$
292 //Get the extension and delivery
293 String ext = FileHelper.getExtension(fso);
295 MimeTypeInfo mimeTypeInfo = getMimeTypeInternal(fso, ext, firstFound);
297 if (mimeTypeInfo != null) {
298 // Create a new drawable
299 if (!TextUtils.isEmpty(mimeTypeInfo.mDrawable)) {
300 return mimeTypeInfo.mDrawable;
303 // Something was wrong here. The resource should exist, but it's not present.
304 // Audit the wrong mime/type resource and return the best fso drawable (probably
306 Log.w(TAG, String.format(
307 "Something was wrong with the drawable of the fso:" + //$NON-NLS-1$
308 "%s, mime: %s", //$NON-NLS-1$
310 mimeTypeInfo.toString()));
315 if (FileHelper.isSystemFile(fso)) {
316 return "fso_type_system_drawable"; //$NON-NLS-1$
318 // Check if the fso is executable (but not a symlink)
319 if (fso.getPermissions() != null && !(fso instanceof Symlink)) {
320 if (fso.getPermissions().getUser().isExecute() ||
321 fso.getPermissions().getGroup().isExecute() ||
322 fso.getPermissions().getOthers().isExecute()) {
323 return "fso_type_executable_drawable"; //$NON-NLS-1$
326 return "ic_fso_default_drawable"; //$NON-NLS-1$
330 * Method that returns the mime/type of the {@link FileSystemObject}.
332 * @param context The current context
333 * @param fso The file system object
334 * @return String The mime/type
336 public static final String getMimeType(Context context, FileSystemObject fso) {
337 //Ensure that mime types are loaded
338 if (sMimeTypes == null) {
339 loadMimeTypes(context);
342 //Directories don't have a mime type
343 if (FileHelper.isDirectory(fso)) {
347 //Get the extension and delivery
348 return getMimeTypeFromExtension(fso);
352 * Method that compares {@link FileSystemObject} by MimeTypeCategory
354 * @param context The current context
355 * @param fso1 File system object 1
356 * @param fso2 File system object 2
357 * @return int Either -1, 0, 1 based on if fso1 appears before or after fso2
359 public static final int compareFSO(Context context, FileSystemObject fso1,
360 FileSystemObject fso2) {
361 MimeTypeCategory mtc1 = getCategory(context, fso1);
362 MimeTypeCategory mtc2 = getCategory(context, fso2);
364 return mtc1.compareTo(mtc2);
368 * Method that returns the mime/type description of the {@link FileSystemObject}.
370 * @param context The current context
371 * @param fso The file system object
372 * @return String The mime/type description
374 public static final String getMimeTypeDescription(Context context, FileSystemObject fso) {
375 Resources res = context.getResources();
377 //Ensure that mime types are loaded
378 if (sMimeTypes == null) {
379 loadMimeTypes(context);
382 //Check if the argument is a folder
383 if (fso instanceof Directory) {
384 return res.getString(R.string.mime_folder);
386 if (fso instanceof Symlink) {
387 return res.getString(R.string.mime_symlink);
391 if (fso instanceof BlockDevice || FileHelper.isSymlinkRefBlockDevice(fso)) {
392 return context.getString(R.string.device_blockdevice);
394 if (fso instanceof CharacterDevice || FileHelper.isSymlinkRefCharacterDevice(fso)) {
395 return context.getString(R.string.device_characterdevice);
397 if (fso instanceof NamedPipe || FileHelper.isSymlinkRefNamedPipe(fso)) {
398 return context.getString(R.string.device_namedpipe);
400 if (fso instanceof DomainSocket || FileHelper.isSymlinkRefDomainSocket(fso)) {
401 return context.getString(R.string.device_domainsocket);
404 //Get the extension and delivery
405 String mime = getMimeTypeFromExtension(fso);
410 return res.getString(R.string.mime_unknown);
414 * Gets the mimetype of a file, if there are multiple possibilities given it's extension.
415 * @param absolutePath The absolute path of the file for which to find the mimetype.
416 * @param ext The extension of the file.
417 * @return The correct mimetype for this file, or null if the mimetype cannot be determined
418 * or is not ambiguous.
420 private static final String getAmbiguousExtensionMimeType(String absolutePath, String ext) {
421 if (AmbiguousExtensionHelper.AMBIGUOUS_EXTENSIONS_MAP.containsKey(ext)) {
422 AmbiguousExtensionHelper helper =
423 AmbiguousExtensionHelper.AMBIGUOUS_EXTENSIONS_MAP.get(ext);
424 String mimeType = helper.getMimeType(absolutePath, ext);
425 if (!TextUtils.isEmpty(mimeType)) {
433 * Get the MimeTypeInfo that describes this file.
434 * @param fso The file.
435 * @param ext The extension of the file.
436 * @return The MimeTypeInfo object that describes this file, or null if it cannot be retrieved.
438 private static final MimeTypeInfo getMimeTypeInternal(FileSystemObject fso, String ext) {
439 return getMimeTypeInternal(fso.getFullPath(), ext);
442 private static final MimeTypeInfo getMimeTypeInternal(FileSystemObject fso,
444 boolean firstFound) {
445 return getMimeTypeInternal(fso.getFullPath(), ext, firstFound);
449 * Get the MimeTypeInfo that describes this file.
450 * @param absolutePath The absolute path of the file.
451 * @param ext The extension of the file.
452 * @return The MimeTypeInfo object that describes this file, or null if it cannot be retrieved.
454 private static final MimeTypeInfo getMimeTypeInternal(String absolutePath, String ext) {
455 return getMimeTypeInternal(absolutePath, ext, false);
458 private static final MimeTypeInfo getMimeTypeInternal(String absolutePath,
460 boolean firstFound) {
461 MimeTypeInfo mimeTypeInfo = null;
462 ArrayList<MimeTypeInfo> mimeTypeInfoList = sMimeTypes.get(ext.toLowerCase(Locale.ROOT));
463 // Multiple mimetypes map to the same extension, try to resolve it.
464 if (mimeTypeInfoList != null && mimeTypeInfoList.size() > 1 && !firstFound) {
465 if (absolutePath != null) {
466 String mimeType = getAmbiguousExtensionMimeType(absolutePath, ext);
467 mimeTypeInfo = sExtensionMimeTypes.get(ext + mimeType);
469 // We don't have the ability to read the file to resolve the ambiguity,
470 // so default to the first available mimetype.
471 mimeTypeInfo = mimeTypeInfoList.get(0);
473 } else if (mimeTypeInfoList != null && mimeTypeInfoList.size() == 1) {
474 // Only one possible mimetype, so pick that one.
475 mimeTypeInfo = mimeTypeInfoList.get(0);
480 private static final String getMimeTypeFromExtension(final FileSystemObject fso) {
481 String ext = FileHelper.getExtension(fso);
486 // If this extension is ambiguous, attempt to resolve it.
487 String mimeType = getAmbiguousExtensionMimeType(fso.getFullPath(), ext);
488 if (mimeType != null) {
492 //Load from the database of mime types
493 MimeTypeInfo mimeTypeInfo = getMimeTypeInternal(fso, ext);
494 if (mimeTypeInfo == null) {
498 return mimeTypeInfo.mMimeType;
502 * Method that returns the mime/type category of the file.
504 * @param context The current context
505 * @param ext The extension of the file
506 * @param absolutePath The absolute path of the file. Can be null if not available.
507 * @return MimeTypeCategory The mime/type category
509 public static final MimeTypeCategory getCategoryFromExt(Context context, String ext,
510 String absolutePath) {
511 // Ensure that have a context
512 if (context == null && sMimeTypes == null) {
514 return MimeTypeCategory.NONE;
516 //Ensure that mime types are loaded
517 if (sMimeTypes == null) {
518 loadMimeTypes(context);
521 //Load from the database of mime types
522 MimeTypeInfo mimeTypeInfo = getMimeTypeInternal(absolutePath, ext);
523 if (mimeTypeInfo != null) {
524 return mimeTypeInfo.mCategory;
529 return MimeTypeCategory.NONE;
533 * Method that returns the mime/type category of the file.
535 * @param context The current context
536 * @param file The file
537 * @return MimeTypeCategory The mime/type category
539 public static final MimeTypeCategory getCategory(Context context, File file) {
540 // Ensure that have a context
541 if (context == null && sMimeTypes == null) {
543 return MimeTypeCategory.NONE;
545 //Ensure that mime types are loaded
546 if (sMimeTypes == null) {
547 loadMimeTypes(context);
550 // Directory and Symlinks no computes as category
551 if (file.isDirectory()) {
552 return MimeTypeCategory.NONE;
555 //Get the extension and delivery
556 return getCategoryFromExt(context,
557 FileHelper.getExtension(file.getName()),
558 file.getAbsolutePath());
562 * Method that returns the mime/type category of the file system object.
564 * @param context The current context
565 * @param fso The file system object
566 * @return MimeTypeCategory The mime/type category
568 public static final MimeTypeCategory getCategory(Context context, FileSystemObject fso) {
569 // Ensure that have a context
570 if (context == null && sMimeTypes == null) {
572 return MimeTypeCategory.NONE;
574 //Ensure that mime types are loaded
575 if (sMimeTypes == null) {
576 loadMimeTypes(context);
579 // Directory and Symlinks no computes as category
580 if (FileHelper.isDirectory(fso)) {
581 return MimeTypeCategory.NONE;
583 if (fso instanceof Symlink) {
584 return MimeTypeCategory.NONE;
587 //Get the extension and delivery
588 final MimeTypeCategory category = getCategoryFromExt(context,
589 FileHelper.getExtension(fso), fso.getFullPath());
592 if (category == MimeTypeCategory.NONE && fso instanceof SystemFile) {
593 return MimeTypeCategory.SYSTEM;
600 * Method that returns the description of the category
602 * @param context The current context
603 * @param category The category
604 * @return String The description of the category
606 public static final String getCategoryDescription(
607 Context context, MimeTypeCategory category) {
608 if (category == null || category.compareTo(MimeTypeCategory.NONE) == 0) {
609 return "-"; //$NON-NLS-1$
612 String id = "category_" + category.toString().toLowerCase(Locale.ROOT); //$NON-NLS-1$
613 int resid = ResourcesHelper.getIdentifier(
614 context.getResources(), "string", id); //$NON-NLS-1$
615 return context.getString(resid);
616 } catch (Throwable e) {/**NON BLOCK**/}
617 return "-"; //$NON-NLS-1$
621 * Method that returns if a file system object matches with a mime-type expression.
623 * @param ctx The current context
624 * @param fso The file system object to check
625 * @param mimeTypeExpression The mime-type expression (xe: */*, audio/*)
626 * @return boolean If the file system object matches the mime-type expression
628 public static final boolean matchesMimeType(
629 Context ctx, FileSystemObject fso, String mimeTypeExpression) {
630 String mimeType = getMimeType(ctx, fso);
631 if (mimeType == null) return false;
632 return mimeType.matches(convertToRegExp(mimeTypeExpression));
636 * Method that loads the mime type information.
638 * @param context The current context
640 //IMP! This must be invoked from the main activity creation
641 public static synchronized void loadMimeTypes(Context context) {
642 if (sMimeTypes == null) {
644 // Load the mime/type database
645 Properties mimeTypes = new Properties();
646 mimeTypes.load(context.getResources().openRawResource(R.raw.mime_types));
648 // Parse the properties to an in-memory structure
649 // Format: <extension> = <category> | <mime type> | <drawable>
650 sMimeTypes = new HashMap<String, ArrayList<MimeTypeInfo>>();
651 sExtensionMimeTypes = new HashMap<String, MimeTypeInfo>();
652 Enumeration<Object> e = mimeTypes.keys();
653 while (e.hasMoreElements()) {
655 String extension = (String)e.nextElement();
656 String data = mimeTypes.getProperty(extension);
657 String[] datas = data.split(",");
658 for (String theData : datas) {
659 String[] mimeData = theData.split("\\|"); //$NON-NLS-1$
661 // Create a reference of MimeType
662 MimeTypeInfo mimeTypeInfo = new MimeTypeInfo();
663 mimeTypeInfo.mCategory = MimeTypeCategory.valueOf(mimeData[0].trim());
664 mimeTypeInfo.mMimeType = mimeData[1].trim();
665 mimeTypeInfo.mDrawable = mimeData[2].trim();
667 // If no list exists yet for this mimetype, create one.
668 // Else, add it to the existing list.
669 if (sMimeTypes.get(extension) == null) {
670 ArrayList<MimeTypeInfo> infoList = new ArrayList<MimeTypeInfo>();
671 infoList.add(mimeTypeInfo);
672 sMimeTypes.put(extension, infoList);
674 sMimeTypes.get(extension).add(mimeTypeInfo);
676 sExtensionMimeTypes.put(extension + mimeTypeInfo.mMimeType,
680 } catch (Exception e2) { /**NON BLOCK**/}
683 } catch (Exception e) {
684 Log.e(TAG, "Fail to load mime types raw file.", e); //$NON-NLS-1$
690 * Method that converts the mime-type expression to a regular expression
692 * @param mimeTypeExpression The mime-type expression
693 * @return String The regular expression
695 private static String convertToRegExp(String mimeTypeExpression) {
696 return mimeTypeExpression.replaceAll("\\*", ".\\*"); //$NON-NLS-1$ //$NON-NLS-2$
701 * Class for resolve known mime types
703 public static final class KnownMimeTypeResolver {
704 private static final String MIME_TYPE_APK = "application/vnd.android.package-archive";
707 * Method that returns if the FileSystemObject is an Android app.
709 * @param context The current context
710 * @param fso The FileSystemObject to check
711 * @return boolean If the FileSystemObject is an Android app.
713 public static boolean isAndroidApp(Context context, FileSystemObject fso) {
714 return MIME_TYPE_APK.equals(MimeTypeHelper.getMimeType(context, fso));
718 * Method that returns if the FileSystemObject is an image file.
720 * @param context The current context
721 * @param fso The FileSystemObject to check
722 * @return boolean If the FileSystemObject is an image file.
724 public static boolean isImage(Context context, FileSystemObject fso) {
725 return MimeTypeHelper.getCategory(context, fso).compareTo(MimeTypeCategory.IMAGE) == 0;
729 * Method that returns if the FileSystemObject is an video file.
731 * @param context The current context
732 * @param fso The FileSystemObject to check
733 * @return boolean If the FileSystemObject is an video file.
735 public static boolean isVideo(Context context, FileSystemObject fso) {
736 return MimeTypeHelper.getCategory(context, fso).compareTo(MimeTypeCategory.VIDEO) == 0;
740 * Method that returns if the File is an image file.
742 * @param context The current context
743 * @param file The File to check
744 * @return boolean If the File is an image file.
746 public static boolean isImage(Context context, File file) {
747 return MimeTypeHelper.getCategory(context, file).compareTo(MimeTypeCategory.IMAGE) == 0;
751 * Method that returns if the File is an video file.
753 * @param context The current context
754 * @param file The File to check
755 * @return boolean If the File is an video file.
757 public static boolean isVideo(Context context, File file) {
758 return MimeTypeHelper.getCategory(context, file).compareTo(MimeTypeCategory.VIDEO) == 0;
762 * Method that returns if the File is an audio file.
764 * @param context The current context
765 * @param file The File to check
766 * @return boolean If the File is an audio file.
768 public static boolean isAudio(Context context, File file) {
769 return MimeTypeHelper.getCategory(context, file).compareTo(MimeTypeCategory.AUDIO) == 0;