17b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira/**
27b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * Copyright (c) 2010, Google Inc.
37b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira *
47b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License");
57b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * you may not use this file except in compliance with the License.
67b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * You may obtain a copy of the License at
77b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira *
87b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira *     http://www.apache.org/licenses/LICENSE-2.0
97b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira *
107b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * Unless required by applicable law or agreed to in writing, software
117b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS,
127b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
137b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * See the License for the specific language governing permissions and
147b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira * limitations under the License.
157b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira */
1630e2c24b056542f3b1b438aeb798305d1226d0c8Andy Huangpackage com.android.mail.utils;
177b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
187b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport android.content.Context;
197b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport android.content.Intent;
207b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport android.content.pm.PackageManager;
217b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport android.content.pm.ResolveInfo;
227b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport android.net.Uri;
23b5d3874d41da961fa58adbeb9e6ba171261472cePaul Westbrookimport android.text.TextUtils;
24b5d3874d41da961fa58adbeb9e6ba171261472cePaul Westbrookimport android.webkit.MimeTypeMap;
257b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
267b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport com.google.common.annotations.VisibleForTesting;
27b5d3874d41da961fa58adbeb9e6ba171261472cePaul Westbrookimport com.google.common.collect.ImmutableSet;
287b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
297b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport java.util.List;
307b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereiraimport java.util.Set;
317b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
327b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira/**
3388fc42e48ee4e927eb77e5cab23f2f5151cac649Andy Huang * Utilities for working with different content types within Mail.
347b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira */
357b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereirapublic class MimeType {
36b5d3874d41da961fa58adbeb9e6ba171261472cePaul Westbrook    private static final String LOG_TAG = LogTag.getLogTag();
37b5d3874d41da961fa58adbeb9e6ba171261472cePaul Westbrook
387b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    public static final String ANDROID_ARCHIVE = "application/vnd.android.package-archive";
397b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    private static final String TEXT_PLAIN = "text/plain";
407b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    @VisibleForTesting
417b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    static final String GENERIC_MIMETYPE = "application/octet-stream";
427b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
437b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    @VisibleForTesting
448d4791ce70b3f71cc90256bc4b5b236379b56331Andrew Sapperstein    private static final Set<String> EML_ATTACHMENT_CONTENT_TYPES = ImmutableSet.of(
45376294bbb5ded471ad577fdb492875a837021d08Andrew Sapperstein            "message/rfc822", "application/eml");
46f86e023107de3647d6c862cc6d6b45a3d7887e77Andrew Sapperstein    public static final String EML_ATTACHMENT_CONTENT_TYPE = "message/rfc822";
477b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    private static final String NULL_ATTACHMENT_CONTENT_TYPE = "null";
487b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    private static final Set<String> UNACCEPTABLE_ATTACHMENT_TYPES = ImmutableSet.of(
497b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            "application/zip", "application/x-gzip", "application/x-bzip2",
507b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            "application/x-compress", "application/x-compressed", "application/x-tar");
517b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
527b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    /**
537b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     * Returns whether or not an attachment of the specified type is installable (e.g. an apk).
547b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     */
557b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    public static boolean isInstallable(String type) {
567b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        return ANDROID_ARCHIVE.equals(type);
577b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    }
587b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
597b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    /**
607b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     * Returns whether or not an attachment of the specified type is viewable.
617b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     */
628750066b10f80e2a8080016973b3296d76d18266Mark Wei    public static boolean isViewable(Context context, Uri contentUri, String contentType) {
637b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        // The provider returns a contentType of "null" instead of null, when the
647b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        // content type is not known.  Changing the provider to return null,
657b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        // breaks other areas that will need to be fixed in a later CL.
667b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        // Bug 2922948 has been written up to track this
677b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        if (contentType == null || contentType.length() == 0 ||
687b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira                NULL_ATTACHMENT_CONTENT_TYPE.equals(contentType)) {
69b5d3874d41da961fa58adbeb9e6ba171261472cePaul Westbrook            LogUtils.d(LOG_TAG, "Attachment with null content type. '%s", contentUri);
707b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            return false;
717b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        }
727b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
737b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        if (isBlocked(contentType)) {
74b5d3874d41da961fa58adbeb9e6ba171261472cePaul Westbrook            LogUtils.d(LOG_TAG, "content type '%s' is blocked. '%s", contentType, contentUri);
757b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            return false;
767b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        }
777b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
78b5d3874d41da961fa58adbeb9e6ba171261472cePaul Westbrook        final Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW);
79b5d3874d41da961fa58adbeb9e6ba171261472cePaul Westbrook        mimetypeIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
80b5d3874d41da961fa58adbeb9e6ba171261472cePaul Westbrook                | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
81b5d3874d41da961fa58adbeb9e6ba171261472cePaul Westbrook
8222283e78ade4e89b5465fb6ed14774d90fd277b5Paul Westbrook        if (contentUri != null) {
838750066b10f80e2a8080016973b3296d76d18266Mark Wei            Utils.setIntentDataAndTypeAndNormalize(mimetypeIntent, contentUri, contentType);
8422283e78ade4e89b5465fb6ed14774d90fd277b5Paul Westbrook        } else {
858750066b10f80e2a8080016973b3296d76d18266Mark Wei            Utils.setIntentTypeAndNormalize(mimetypeIntent, contentType);
8622283e78ade4e89b5465fb6ed14774d90fd277b5Paul Westbrook        }
877b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
887b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        PackageManager manager;
897b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        // We need to catch the exception to make CanvasConversationHeaderView
907b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        // test pass.  Bug: http://b/issue?id=3470653.
917b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        try {
927b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            manager = context.getPackageManager();
937b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        } catch (UnsupportedOperationException e) {
947b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            return false;
957b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        }
96b5d3874d41da961fa58adbeb9e6ba171261472cePaul Westbrook        final List<ResolveInfo> list = manager.queryIntentActivities(mimetypeIntent,
97b5d3874d41da961fa58adbeb9e6ba171261472cePaul Westbrook                PackageManager.MATCH_DEFAULT_ONLY);
989faf88ab9c0a36b097c671f8ab4154f8390742a7Paul Westbrook        if (list.size() == 0) {
999faf88ab9c0a36b097c671f8ab4154f8390742a7Paul Westbrook            // This logging will help track down bug 7092215.  Once that bug is resolved, remove
1009faf88ab9c0a36b097c671f8ab4154f8390742a7Paul Westbrook            // this.
1019faf88ab9c0a36b097c671f8ab4154f8390742a7Paul Westbrook            LogUtils.w(LOG_TAG, "Unable to find supporting activity. " +
1029faf88ab9c0a36b097c671f8ab4154f8390742a7Paul Westbrook                    "mime-type: %s, uri: %s, normalized mime-type: %s normalized uri: %s",
1039faf88ab9c0a36b097c671f8ab4154f8390742a7Paul Westbrook                    contentType, contentUri, mimetypeIntent.getType(), mimetypeIntent.getData());
1049faf88ab9c0a36b097c671f8ab4154f8390742a7Paul Westbrook        }
1057b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        return list.size() > 0;
1067b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    }
1077b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
1087b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    /**
1097b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     * @return whether the specified type is blocked.
1107b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     */
1117b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    public static boolean isBlocked(String contentType) {
1127b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        return UNACCEPTABLE_ATTACHMENT_TYPES.contains(contentType);
1137b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    }
1147b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
1157b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    /**
1167b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     * Extract and return filename's extension, converted to lower case, and not including the "."
1177b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     *
1187b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     * @return extension, or null if not found (or null/empty filename)
1197b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     */
1207b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    private static String getFilenameExtension(String fileName) {
1217b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        String extension = null;
1227b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        if (!TextUtils.isEmpty(fileName)) {
1237b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            int lastDot = fileName.lastIndexOf('.');
1247b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            if ((lastDot > 0) && (lastDot < fileName.length() - 1)) {
1257b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira                extension = fileName.substring(lastDot + 1).toLowerCase();
1267b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            }
1277b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        }
1287b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        return extension;
1297b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    }
1307b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
1317b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
1327b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    /**
1337b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     * Returns the mime type of the attachment based on its name and
1347b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     * original mime type. This is an workaround for bugs where Gmail
1357b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     * server doesn't set content-type for certain types correctly.
136f86e023107de3647d6c862cc6d6b45a3d7887e77Andrew Sapperstein     * 1) EML files -> "message/rfc822".
1377b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     * @param name name of the attachment.
1387b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     * @param mimeType original mime type of the attachment.
1397b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     * @return the inferred mime type of the attachment.
1407b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira     */
1417b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    public static String inferMimeType(String name, String mimeType) {
1427b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        final String extension = getFilenameExtension(name);
1437b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        if (TextUtils.isEmpty(extension)) {
1447b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            // Attachment doesn't have extension, just return original mime
1457b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            // type.
1467b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            return mimeType;
1477b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        } else {
1487b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            final boolean isTextPlain = TEXT_PLAIN.equalsIgnoreCase(mimeType);
1497b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            final boolean isGenericType =
1507b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira                    isTextPlain || GENERIC_MIMETYPE.equalsIgnoreCase(mimeType);
1517b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira
1527b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            String type = null;
1537b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            if (isGenericType || TextUtils.isEmpty(mimeType)) {
1547b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira                type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
1557b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            }
1567b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            if (!TextUtils.isEmpty(type)) {
1577b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira                return type;
1587b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            } if (extension.equals("eml")) {
159f86e023107de3647d6c862cc6d6b45a3d7887e77Andrew Sapperstein                // Extension is ".eml", return mime type "message/rfc822"
1607b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira                return EML_ATTACHMENT_CONTENT_TYPE;
1617b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            } else {
1627b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira                // Extension is not ".eml", just return original mime type.
1637b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira                return !TextUtils.isEmpty(mimeType) ? mimeType : GENERIC_MIMETYPE;
1647b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira            }
1657b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira        }
1667b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira    }
1678d4791ce70b3f71cc90256bc4b5b236379b56331Andrew Sapperstein
1688d4791ce70b3f71cc90256bc4b5b236379b56331Andrew Sapperstein    /**
1698d4791ce70b3f71cc90256bc4b5b236379b56331Andrew Sapperstein     * Checks the supplied mime type to determine if it is a valid eml file.
1708d4791ce70b3f71cc90256bc4b5b236379b56331Andrew Sapperstein     * Valid mime types are "message/rfc822" and "application/eml".
1718d4791ce70b3f71cc90256bc4b5b236379b56331Andrew Sapperstein     * @param mimeType the mime type to check
1728d4791ce70b3f71cc90256bc4b5b236379b56331Andrew Sapperstein     * @return {@code true} if the mime type is one of the valid mime types.
1738d4791ce70b3f71cc90256bc4b5b236379b56331Andrew Sapperstein     */
1748d4791ce70b3f71cc90256bc4b5b236379b56331Andrew Sapperstein    public static boolean isEmlMimeType(String mimeType) {
1758d4791ce70b3f71cc90256bc4b5b236379b56331Andrew Sapperstein        return EML_ATTACHMENT_CONTENT_TYPES.contains(mimeType);
1768d4791ce70b3f71cc90256bc4b5b236379b56331Andrew Sapperstein    }
1777b56a61174eeb202eea468b7f68b79729737ded2Mindy Pereira}
178