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