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 
AttachmentInfo(Context context, Attachment attachment)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 
AttachmentInfo(Context context, Cursor c)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 
AttachmentInfo(Context context, AttachmentInfo info)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 
AttachmentInfo(Context context, long id, long size, String fileName, String mimeType, long accountKey, int flags)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      */
getAttachmentIntent(Context context, long accountId)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 
getUriForIntent(Context context, long accountId)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      */
isEligibleForDownload()223     public boolean isEligibleForDownload() {
224         return mAllowView || mAllowSave;
225     }
226 
227     @Override
hashCode()228     public int hashCode() {
229         return (int) (mId ^ (mId >>> 32));
230     }
231 
232     @Override
equals(Object o)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
toString()246     public String toString() {
247         return "{Attachment " + mId + ":" + mName + "," + mContentType + "," + mSize + "}";
248     }
249 }
250