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