Home | History | Annotate | Download | only in email
      1 /*
      2  * Copyright (C) 2011 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.email;
     18 
     19 import com.android.emailcommon.internet.MimeUtility;
     20 import com.android.emailcommon.provider.EmailContent.Attachment;
     21 import com.android.emailcommon.provider.EmailContent.AttachmentColumns;
     22 import com.android.emailcommon.utility.AttachmentUtilities;
     23 import com.android.emailcommon.utility.Utility;
     24 
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.ResolveInfo;
     29 import android.database.Cursor;
     30 import android.net.ConnectivityManager;
     31 import android.net.Uri;
     32 import android.provider.Settings;
     33 import android.text.TextUtils;
     34 
     35 import java.util.List;
     36 
     37 /**
     38  * Encapsulates commonly used attachment information related to suitability for viewing and saving,
     39  * based on the attachment's filename and mimetype.
     40  */
     41 public class AttachmentInfo {
     42     // Projection which can be used with the constructor taking a Cursor argument
     43     public static final String[] PROJECTION = {
     44             AttachmentColumns._ID,
     45             AttachmentColumns.SIZE,
     46             AttachmentColumns.FILENAME,
     47             AttachmentColumns.MIME_TYPE,
     48             AttachmentColumns.ACCOUNT_KEY,
     49             AttachmentColumns.FLAGS
     50     };
     51     // Offsets into PROJECTION
     52     public static final int COLUMN_ID = 0;
     53     public static final int COLUMN_SIZE = 1;
     54     public static final int COLUMN_FILENAME = 2;
     55     public static final int COLUMN_MIME_TYPE = 3;
     56     public static final int COLUMN_ACCOUNT_KEY = 4;
     57     public static final int COLUMN_FLAGS = 5;
     58 
     59     /** Attachment not denied */
     60     public static final int ALLOW           = 0x00;
     61     /** Attachment suspected of being malware */
     62     public static final int DENY_MALWARE    = 0x01;
     63     /** Attachment too large; must download over wi-fi */
     64     public static final int DENY_WIFIONLY   = 0x02;
     65     /** No receiving intent to handle attachment type */
     66     public static final int DENY_NOINTENT   = 0x04;
     67     /** Side load of applications is disabled */
     68     public static final int DENY_NOSIDELOAD = 0x08;
     69     // TODO Remove DENY_APKINSTALL when we can install directly from the Email activity
     70     /** Unable to install any APK */
     71     public static final int DENY_APKINSTALL = 0x10;
     72     /** Security policy prohibits install */
     73     public static final int DENY_POLICY = 0x20;
     74 
     75     public final long mId;
     76     public final long mSize;
     77     public final String mName;
     78     public final String mContentType;
     79     public final long mAccountKey;
     80     public final int mFlags;
     81 
     82     /** Whether or not this attachment can be viewed */
     83     public final boolean mAllowView;
     84     /** Whether or not this attachment can be saved */
     85     public final boolean mAllowSave;
     86     /** Whether or not this attachment can be installed [only true for APKs] */
     87     public final boolean mAllowInstall;
     88     /** Reason(s) why this attachment is denied from being viewed */
     89     public final int mDenyFlags;
     90 
     91     public AttachmentInfo(Context context, Attachment attachment) {
     92         this(context, attachment.mId, attachment.mSize, attachment.mFileName, attachment.mMimeType,
     93                 attachment.mAccountKey, attachment.mFlags);
     94     }
     95 
     96     public AttachmentInfo(Context context, Cursor c) {
     97         this(context, c.getLong(COLUMN_ID), c.getLong(COLUMN_SIZE), c.getString(COLUMN_FILENAME),
     98                 c.getString(COLUMN_MIME_TYPE), c.getLong(COLUMN_ACCOUNT_KEY),
     99                 c.getInt(COLUMN_FLAGS));
    100     }
    101 
    102     public AttachmentInfo(Context context, AttachmentInfo info) {
    103         this(context, info.mId, info.mSize, info.mName, info.mContentType, info.mAccountKey,
    104                 info.mFlags);
    105     }
    106 
    107     public AttachmentInfo(Context context, long id, long size, String fileName, String mimeType,
    108             long accountKey, int flags) {
    109         mSize = size;
    110         mContentType = AttachmentUtilities.inferMimeType(fileName, mimeType);
    111         mName = fileName;
    112         mId = id;
    113         mAccountKey = accountKey;
    114         mFlags = flags;
    115         boolean canView = true;
    116         boolean canSave = true;
    117         boolean canInstall = false;
    118         int denyFlags = ALLOW;
    119 
    120         // Don't enable the "save" button if we've got no place to save the file
    121         if (!Utility.isExternalStorageMounted()) {
    122             canSave = false;
    123         }
    124 
    125         // Check for acceptable / unacceptable attachments by MIME-type
    126         if ((!MimeUtility.mimeTypeMatches(mContentType,
    127                 AttachmentUtilities.ACCEPTABLE_ATTACHMENT_VIEW_TYPES)) ||
    128             (MimeUtility.mimeTypeMatches(mContentType,
    129                     AttachmentUtilities.UNACCEPTABLE_ATTACHMENT_VIEW_TYPES))) {
    130             canView = false;
    131         }
    132 
    133         // Check for unacceptable attachments by filename extension
    134         String extension = AttachmentUtilities.getFilenameExtension(mName);
    135         if (!TextUtils.isEmpty(extension) &&
    136                 Utility.arrayContains(AttachmentUtilities.UNACCEPTABLE_ATTACHMENT_EXTENSIONS,
    137                         extension)) {
    138             canView = false;
    139             canSave = false;
    140             denyFlags |= DENY_MALWARE;
    141         }
    142 
    143         // Check for policy restrictions on download
    144         if ((flags & Attachment.FLAG_POLICY_DISALLOWS_DOWNLOAD) != 0) {
    145             canView = false;
    146             canSave = false;
    147             denyFlags |= DENY_POLICY;
    148         }
    149 
    150         // Check for installable attachments by filename extension
    151         extension = AttachmentUtilities.getFilenameExtension(mName);
    152         if (!TextUtils.isEmpty(extension) &&
    153                 Utility.arrayContains(AttachmentUtilities.INSTALLABLE_ATTACHMENT_EXTENSIONS,
    154                         extension)) {
    155             boolean sideloadEnabled;
    156             sideloadEnabled = Settings.Secure.getInt(context.getContentResolver(),
    157                     Settings.Secure.INSTALL_NON_MARKET_APPS, 0 /* sideload disabled */) == 1;
    158             canSave &= sideloadEnabled;
    159             canView = canSave;
    160             canInstall = canSave;
    161             if (!sideloadEnabled) {
    162                 denyFlags |= DENY_NOSIDELOAD;
    163             }
    164         }
    165 
    166         // Check for file size exceeded
    167         // The size limit is overridden when on a wifi connection - any size is OK
    168         if (mSize > AttachmentUtilities.MAX_ATTACHMENT_DOWNLOAD_SIZE) {
    169             int networkType = EmailConnectivityManager.getActiveNetworkType(context);
    170             if (networkType != ConnectivityManager.TYPE_WIFI) {
    171                 canView = false;
    172                 canSave = false;
    173                 denyFlags |= DENY_WIFIONLY;
    174             }
    175         }
    176 
    177         // Check to see if any activities can view this attachment; if none, we can't view it
    178         Intent intent = getAttachmentIntent(context, 0);
    179         PackageManager pm = context.getPackageManager();
    180         List<ResolveInfo> activityList = pm.queryIntentActivities(intent, 0 /*no account*/);
    181         if (activityList.isEmpty()) {
    182             canView = false;
    183             canSave = false;
    184             denyFlags |= DENY_NOINTENT;
    185         }
    186 
    187         mAllowView = canView;
    188         mAllowSave = canSave;
    189         mAllowInstall = canInstall;
    190         mDenyFlags = denyFlags;
    191     }
    192 
    193     /**
    194      * Returns an <code>Intent</code> to load the given attachment.
    195      * @param context the caller's context
    196      * @param accountId the account associated with the attachment (or 0 if we don't need to
    197      *     resolve from attachmentUri to contentUri)
    198      * @return an Intent suitable for viewing the attachment
    199      */
    200     public Intent getAttachmentIntent(Context context, long accountId) {
    201         Uri contentUri = getUriForIntent(context, accountId);
    202         Intent intent = new Intent(Intent.ACTION_VIEW);
    203         intent.setDataAndType(contentUri, mContentType);
    204         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
    205                 | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
    206         return intent;
    207     }
    208 
    209     protected Uri getUriForIntent(Context context, long accountId) {
    210         Uri contentUri = AttachmentUtilities.getAttachmentUri(accountId, mId);
    211         if (accountId > 0) {
    212             contentUri = AttachmentUtilities.resolveAttachmentIdToContentUri(
    213                     context.getContentResolver(), contentUri);
    214         }
    215 
    216         return contentUri;
    217     }
    218 
    219     /**
    220      * An attachment is eligible for download if it can either be viewed or saved (or both)
    221      * @return whether the attachment is eligible for download
    222      */
    223     public boolean isEligibleForDownload() {
    224         return mAllowView || mAllowSave;
    225     }
    226 
    227     @Override
    228     public int hashCode() {
    229         return (int) (mId ^ (mId >>> 32));
    230     }
    231 
    232     @Override
    233     public boolean equals(Object o) {
    234         if (o == this) {
    235             return true;
    236         }
    237 
    238         if ((o == null) || (o.getClass() != getClass())) {
    239             return false;
    240         }
    241 
    242         return ((AttachmentInfo) o).mId == mId;
    243     }
    244 
    245     @Override
    246     public String toString() {
    247         return "{Attachment " + mId + ":" + mName + "," + mContentType + "," + mSize + "}";
    248     }
    249 }
    250