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