AttachmentInfo.java revision 3f60e9312b1bf1324dd0e343b07cb69c9f8d31fd
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
17package com.android.email;
18
19import com.android.emailcommon.internet.MimeUtility;
20import com.android.emailcommon.provider.EmailContent.Attachment;
21import com.android.emailcommon.utility.AttachmentUtilities;
22import com.android.emailcommon.utility.Utility;
23
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.PackageManager;
27import android.content.pm.ResolveInfo;
28import android.database.Cursor;
29import android.net.ConnectivityManager;
30import android.net.NetworkInfo;
31import android.net.Uri;
32import android.provider.Settings;
33import android.text.TextUtils;
34
35import 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 mime type.
40 */
41public class AttachmentInfo {
42    // Projection which can be used with the constructor taking a Cursor argument
43    public static final String[] PROJECTION = new String[] {Attachment.RECORD_ID, Attachment.SIZE,
44        Attachment.FILENAME, Attachment.MIME_TYPE, Attachment.ACCOUNT_KEY};
45    // Offsets into PROJECTION
46    public static final int COLUMN_ID = 0;
47    public static final int COLUMN_SIZE = 1;
48    public static final int COLUMN_FILENAME = 2;
49    public static final int COLUMN_MIME_TYPE = 3;
50    public static final int COLUMN_ACCOUNT_KEY = 4;
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
66    public final long mId;
67    public final long mSize;
68    public final String mName;
69    public final String mContentType;
70    public final long mAccountKey;
71
72    /** Whether or not this attachment can be viewed */
73    public final boolean mAllowView;
74    /** Whether or not this attachment can be saved */
75    public final boolean mAllowSave;
76    /** Whether or not this attachment can be installed [only true for APKs] */
77    public final boolean mAllowInstall;
78    /** Reason(s) why this attachment is denied from being viewed */
79    public final int mDenyFlags;
80
81    public AttachmentInfo(Context context, Attachment attachment) {
82        this(context, attachment.mId, attachment.mSize, attachment.mFileName, attachment.mMimeType,
83                attachment.mAccountKey);
84    }
85
86    public AttachmentInfo(Context context, Cursor c) {
87        this(context, c.getLong(COLUMN_ID), c.getLong(COLUMN_SIZE), c.getString(COLUMN_FILENAME),
88                c.getString(COLUMN_MIME_TYPE), c.getLong(COLUMN_ACCOUNT_KEY));
89    }
90
91    public AttachmentInfo(Context context, AttachmentInfo info) {
92        this(context, info.mId, info.mSize, info.mName, info.mContentType, info.mAccountKey);
93    }
94
95    public AttachmentInfo(Context context, long id, long size, String fileName, String mimeType,
96            long accountKey) {
97        mSize = size;
98        mContentType = AttachmentUtilities.inferMimeType(fileName, mimeType);
99        mName = fileName;
100        mId = id;
101        mAccountKey = accountKey;
102        boolean canView = true;
103        boolean canSave = true;
104        boolean canInstall = false;
105        int denyFlags = ALLOW;
106
107        // Don't enable the "save" button if we've got no place to save the file
108        if (!Utility.isExternalStorageMounted()) {
109            canSave = false;
110        }
111
112        // Check for acceptable / unacceptable attachments by MIME-type
113        if ((!MimeUtility.mimeTypeMatches(mContentType,
114                AttachmentUtilities.ACCEPTABLE_ATTACHMENT_VIEW_TYPES)) ||
115            (MimeUtility.mimeTypeMatches(mContentType,
116                    AttachmentUtilities.UNACCEPTABLE_ATTACHMENT_VIEW_TYPES))) {
117            canView = false;
118        }
119
120        // Check for unacceptable attachments by filename extension
121        String extension = AttachmentUtilities.getFilenameExtension(mName);
122        if (!TextUtils.isEmpty(extension) &&
123                Utility.arrayContains(AttachmentUtilities.UNACCEPTABLE_ATTACHMENT_EXTENSIONS,
124                        extension)) {
125            canView = false;
126            canSave = false;
127            denyFlags |= DENY_MALWARE;
128        }
129
130        // Check for installable attachments by filename extension
131        extension = AttachmentUtilities.getFilenameExtension(mName);
132        if (!TextUtils.isEmpty(extension) &&
133                Utility.arrayContains(AttachmentUtilities.INSTALLABLE_ATTACHMENT_EXTENSIONS,
134                        extension)) {
135            boolean sideloadEnabled;
136            sideloadEnabled = Settings.Secure.getInt(context.getContentResolver(),
137                    Settings.Secure.INSTALL_NON_MARKET_APPS, 0 /* sideload disabled */) == 1;
138            // NOTE: Currently we prevent viewing (or installing) any application attachment
139            // from within Email. When we support installing directly from view, save and
140            // install should all be following sideloadEnabled.
141            canView = false;
142            canSave &= sideloadEnabled;
143            if (!sideloadEnabled) {
144                denyFlags |= DENY_NOSIDELOAD;
145            } else {
146                denyFlags |= DENY_APKINSTALL;
147            }
148            canInstall = canView;
149        }
150
151        // Check for file size exceeded
152        // The size limit is overridden when on a wifi connection - any size is OK
153        if (mSize > AttachmentUtilities.MAX_ATTACHMENT_DOWNLOAD_SIZE) {
154            ConnectivityManager cm = (ConnectivityManager)
155                    context.getSystemService(Context.CONNECTIVITY_SERVICE);
156            NetworkInfo network = cm.getActiveNetworkInfo();
157            if (network == null || network.getType() != ConnectivityManager.TYPE_WIFI) {
158                canView = false;
159                canSave = false;
160                denyFlags |= DENY_WIFIONLY;
161            }
162        }
163
164        // Check to see if any activities can view this attachment; if none, we can't view it
165        Intent intent = getAttachmentIntent(context, 0);
166        PackageManager pm = context.getPackageManager();
167        List<ResolveInfo> activityList = pm.queryIntentActivities(intent, 0 /*no account*/);
168        if (activityList.isEmpty()) {
169            canView = false;
170            canSave = false;
171            denyFlags |= DENY_NOINTENT;
172        }
173
174        mAllowView = canView;
175        mAllowSave = canSave;
176        mAllowInstall = canInstall;
177        mDenyFlags = denyFlags;
178    }
179
180    /**
181     * Returns an <code>Intent</code> to load the given attachment.
182     * @param context the caller's context
183     * @param accountId the account associated with the attachment (or 0 if we don't need to
184     * resolve from attachmentUri to contentUri)
185     * @return an Intent suitable for loading the attachment
186     */
187    public Intent getAttachmentIntent(Context context, long accountId) {
188        Uri contentUri = AttachmentUtilities.getAttachmentUri(accountId, mId);
189        if (accountId > 0) {
190            contentUri = AttachmentUtilities.resolveAttachmentIdToContentUri(
191                    context.getContentResolver(), contentUri);
192        }
193        Intent intent = new Intent(Intent.ACTION_VIEW);
194        intent.setData(contentUri);
195        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
196                | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
197        return intent;
198    }
199
200    /**
201     * An attachment is eligible for download if it can either be viewed or saved (or both)
202     * @return whether the attachment is eligible for download
203     */
204    public boolean isEligibleForDownload() {
205        return mAllowView || mAllowSave;
206    }
207
208    @Override
209    public String toString() {
210        return "{Attachment " + mId + ":" + mName + "," + mContentType + "," + mSize + "}";
211    }
212}
213