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