diff options
author | Tomasz Mikolajewski <mtomasz@google.com> | 2016-12-05 14:10:06 +0900 |
---|---|---|
committer | Tomasz Mikolajewski <mtomasz@google.com> | 2017-03-01 14:42:17 +0900 |
commit | 6b802e8d33c6230907dc0fd0353a808f8e8f5f16 (patch) | |
tree | fd747fceb0bb374c5e51d2b5fe74a1811d21af8d | |
parent | b96eb04b2a8ff4b7d47b09b4b2b5493c032e4d36 (diff) | |
download | android_packages_apps_UnifiedEmail-6b802e8d33c6230907dc0fd0353a808f8e8f5f16.tar.gz android_packages_apps_UnifiedEmail-6b802e8d33c6230907dc0fd0353a808f8e8f5f16.tar.bz2 android_packages_apps_UnifiedEmail-6b802e8d33c6230907dc0fd0353a808f8e8f5f16.zip |
Add basic support for virtual files to Email.
It's basic support which allows users to attach virtual files
in the first provided streamable format, usually PDF.
Bug: 33609522
Test: Tested manually by attaching a sheets document.
Change-Id: Ie75beaadb822c1e97e47c0972447114387e203e5
-rw-r--r-- | src/com/android/mail/compose/AttachmentComposeView.java | 3 | ||||
-rw-r--r-- | src/com/android/mail/compose/AttachmentsView.java | 129 | ||||
-rw-r--r-- | src/com/android/mail/compose/ComposeActivity.java | 56 | ||||
-rw-r--r-- | src/com/android/mail/providers/Attachment.java | 16 | ||||
-rw-r--r-- | src/com/android/mail/providers/UIProvider.java | 9 | ||||
-rw-r--r-- | src/com/android/mail/utils/AttachmentUtils.java | 18 | ||||
-rw-r--r-- | src/com/android/mail/utils/Utils.java | 4 |
7 files changed, 174 insertions, 61 deletions
diff --git a/src/com/android/mail/compose/AttachmentComposeView.java b/src/com/android/mail/compose/AttachmentComposeView.java index 89216e17f..6f099c50e 100644 --- a/src/com/android/mail/compose/AttachmentComposeView.java +++ b/src/com/android/mail/compose/AttachmentComposeView.java @@ -69,7 +69,8 @@ class AttachmentComposeView extends LinearLayout implements AttachmentDeletionIn private void populateAttachmentData(Context context) { ((TextView) findViewById(R.id.attachment_name)).setText(mAttachment.getName()); - if (mAttachment.size > 0) { + // If size is -1, then it's size is unknown, hence do not show it. + if (mAttachment.size != -1) { ((TextView) findViewById(R.id.attachment_size)). setText(AttachmentUtils.convertToHumanReadableSize(context, mAttachment.size)); } else { diff --git a/src/com/android/mail/compose/AttachmentsView.java b/src/com/android/mail/compose/AttachmentsView.java index 5edac8a4d..285a43798 100644 --- a/src/com/android/mail/compose/AttachmentsView.java +++ b/src/com/android/mail/compose/AttachmentsView.java @@ -15,18 +15,21 @@ */ package com.android.mail.compose; +import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.net.Uri; import android.os.ParcelFileDescriptor; +import android.provider.DocumentsContract; import android.provider.OpenableColumns; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; +import android.webkit.MimeTypeMap; import android.widget.LinearLayout; import com.android.mail.R; @@ -37,6 +40,7 @@ import com.android.mail.ui.AttachmentTile.AttachmentPreview; import com.android.mail.ui.AttachmentTileGrid; import com.android.mail.utils.LogTag; import com.android.mail.utils.LogUtils; +import com.android.mail.utils.Utils; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; @@ -206,6 +210,39 @@ class AttachmentsView extends LinearLayout { } /** + * Checks if the passed Uri is a virtual document. + * + * @param contentUri + * @return true if virtual, false if regular. + */ + @TargetApi(24) + private boolean isVirtualDocument(Uri contentUri) { + // For SAF files, check if it's a virtual document. + if (!DocumentsContract.isDocumentUri(getContext(), contentUri)) { + return false; + } + + final ContentResolver contentResolver = getContext().getContentResolver(); + final Cursor metadataCursor = contentResolver.query(contentUri, + new String[] { DocumentsContract.Document.COLUMN_FLAGS }, null, null, null); + if (metadataCursor != null) { + try { + int flags = 0; + if (metadataCursor.moveToNext()) { + flags = metadataCursor.getInt(0); + } + if ((flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0) { + return true; + } + } finally { + metadataCursor.close(); + } + } + + return false; + } + + /** * Generate an {@link Attachment} object for a given local content URI. Attempts to populate * the {@link Attachment#name}, {@link Attachment#size}, and {@link Attachment#contentType} * fields using a {@link ContentResolver}. @@ -226,13 +263,26 @@ class AttachmentsView extends LinearLayout { if (contentType == null) contentType = ""; final Attachment attachment = new Attachment(); - attachment.uri = null; // URI will be assigned by the provider upon send/save - attachment.setName(null); - attachment.size = 0; + attachment.uri = null; // URI will be assigned by the provider upon send/save attachment.contentUri = contentUri; attachment.thumbnailUri = contentUri; Cursor metadataCursor = null; + String name = null; + int size = -1; // Unknown, will be determined either now or in the service + final boolean isVirtual = Utils.isRunningNOrLater() + ? isVirtualDocument(contentUri) : false; + + if (isVirtual) { + final String[] mimeTypes = contentResolver.getStreamTypes(contentUri, "*/*"); + if (mimeTypes != null && mimeTypes.length > 0) { + attachment.virtualMimeType = mimeTypes[0]; + } else{ + throw new AttachmentFailureException( + "Cannot attach a virtual document without any streamable format."); + } + } + try { metadataCursor = contentResolver.query( contentUri, new String[]{OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, @@ -240,8 +290,12 @@ class AttachmentsView extends LinearLayout { if (metadataCursor != null) { try { if (metadataCursor.moveToNext()) { - attachment.setName(metadataCursor.getString(0)); - attachment.size = metadataCursor.getInt(1); + name = metadataCursor.getString(0); + // For virtual document this size is not the one which will be attached, + // so ignore it. + if (!isVirtual) { + size = metadataCursor.getInt(1); + } } } finally { metadataCursor.close(); @@ -259,38 +313,55 @@ class AttachmentsView extends LinearLayout { metadataCursor = getOptionalColumn(contentResolver, contentUri, OpenableColumns.DISPLAY_NAME); if (metadataCursor != null && metadataCursor.moveToNext()) { - attachment.setName(metadataCursor.getString(0)); + name = metadataCursor.getString(0); } } finally { if (metadataCursor != null) metadataCursor.close(); } // Let's try to get SIZE - try { - metadataCursor = - getOptionalColumn(contentResolver, contentUri, OpenableColumns.SIZE); - if (metadataCursor != null && metadataCursor.moveToNext()) { - attachment.size = metadataCursor.getInt(0); - } else { - // Unable to get the size from the metadata cursor. Open the file and seek. - attachment.size = getSizeFromFile(contentUri, contentResolver); + if (!isVirtual) { + try { + metadataCursor = + getOptionalColumn(contentResolver, contentUri, OpenableColumns.SIZE); + if (metadataCursor != null && metadataCursor.moveToNext()) { + size = metadataCursor.getInt(0); + } else { + // Unable to get the size from the metadata cursor. Open the file and seek. + size = getSizeFromFile(contentUri, contentResolver); + } + } finally { + if (metadataCursor != null) metadataCursor.close(); } - } finally { - if (metadataCursor != null) metadataCursor.close(); } } catch (SecurityException e) { throw new AttachmentFailureException("Security Exception from attachment uri", e); } - if (attachment.getName() == null) { - attachment.setName(contentUri.getLastPathSegment()); + if (name == null) { + name = contentUri.getLastPathSegment(); } - if (attachment.size == 0) { + + // For virtual files append the inferred extension name. + if (isVirtual) { + String extension = MimeTypeMap.getSingleton() + .getExtensionFromMimeType(attachment.virtualMimeType); + if (extension != null) { + name += "." + extension; + } + } + + // TODO: This can't work with pipes. Fix it. + if (size == -1 && !isVirtual) { // if the attachment is not a content:// for example, a file:// URI - attachment.size = getSizeFromFile(contentUri, contentResolver); + size = getSizeFromFile(contentUri, contentResolver); } + // Save the computed values into the attachment. + attachment.size = size; + attachment.setName(name); attachment.setContentType(contentType); + return attachment; } @@ -300,28 +371,28 @@ class AttachmentsView extends LinearLayout { * @param account * @param attachment the attachment to be added. * - * @return size of the attachment added. * @throws AttachmentFailureException if an error occurs adding the attachment. */ - public long addAttachment(Account account, Attachment attachment) + public void addAttachment(Account account, Attachment attachment) throws AttachmentFailureException { final int maxSize = account.settings.getMaxAttachmentSize(); - // Error getting the size or the size was too big. - if (attachment.size == -1 || attachment.size > maxSize) { + // The attachment size is known and it's too big. + if (attachment.size > maxSize) { throw new AttachmentFailureException( "Attachment too large to attach", R.string.too_large_to_attach_single); - } else if ((getTotalAttachmentsSize() + } else if (attachment.size != -1 && (getTotalAttachmentsSize() + attachment.size) > maxSize) { throw new AttachmentFailureException( "Attachment too large to attach", R.string.too_large_to_attach_additional); } else { addAttachment(attachment); } - - return attachment.size; } + /** + * @return size of the file or -1 if unknown. + */ private static int getSizeFromFile(Uri uri, ContentResolver contentResolver) { int size = -1; ParcelFileDescriptor file = null; @@ -339,9 +410,7 @@ class AttachmentsView extends LinearLayout { LogUtils.w(LOG_TAG, "Error closing file opened to obtain size."); } } - // We only want to return a non-negative value. (ParcelFileDescriptor#getStatSize() will - // return -1 if the fd is not a file - return Math.max(size, 0); + return size; } /** diff --git a/src/com/android/mail/compose/ComposeActivity.java b/src/com/android/mail/compose/ComposeActivity.java index 5f125abe6..7b05ffaa1 100644 --- a/src/com/android/mail/compose/ComposeActivity.java +++ b/src/com/android/mail/compose/ComposeActivity.java @@ -36,6 +36,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.Loader; import android.content.pm.ActivityInfo; +import android.content.res.AssetFileDescriptor; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Rect; @@ -1834,12 +1835,16 @@ public class ComposeActivity extends ActionBarActivity addAttachments(refMessage.getAttachments()); } - public long addAttachments(List<Attachment> attachments) { - long size = 0; + /** + * @return true if at least one file is attached. + */ + public boolean addAttachments(List<Attachment> attachments) { + boolean attached = false; AttachmentFailureException error = null; for (Attachment a : attachments) { try { - size += mAttachmentsView.addAttachment(mAccount, a); + mAttachmentsView.addAttachment(mAccount, a); + attached = true; } catch (AttachmentFailureException e) { error = e; } @@ -1852,7 +1857,7 @@ public class ComposeActivity extends ActionBarActivity showAttachmentTooBigToast(error.getErrorRes()); } } - return size; + return attached; } /** @@ -1881,33 +1886,30 @@ public class ComposeActivity extends ActionBarActivity } final String action = intent.getAction(); if (!mAttachmentsChanged) { - long totalSize = 0; + boolean attached = false; if (extras.containsKey(EXTRA_ATTACHMENTS)) { final String[] uris = (String[]) extras.getSerializable(EXTRA_ATTACHMENTS); final ArrayList<Uri> parsedUris = Lists.newArrayListWithCapacity(uris.length); for (String uri : uris) { parsedUris.add(Uri.parse(uri)); } - totalSize += handleAttachmentUrisFromIntent(parsedUris); + attached |= handleAttachmentUrisFromIntent(parsedUris); } if (extras.containsKey(Intent.EXTRA_STREAM)) { if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { final ArrayList<Uri> uris = extras .getParcelableArrayList(Intent.EXTRA_STREAM); - totalSize += handleAttachmentUrisFromIntent(uris); + attached |= handleAttachmentUrisFromIntent(uris); } else { final Uri uri = extras.getParcelable(Intent.EXTRA_STREAM); final ArrayList<Uri> uris = Lists.newArrayList(uri); - totalSize += handleAttachmentUrisFromIntent(uris); + attached |= handleAttachmentUrisFromIntent(uris); } } - if (totalSize > 0) { + if (attached) { mAttachmentsChanged = true; updateSaveUi(); - - Analytics.getInstance().sendEvent("send_intent_with_attachments", - Integer.toString(getAttachments().size()), null, totalSize); } } } @@ -1923,9 +1925,9 @@ public class ComposeActivity extends ActionBarActivity /** * Helper function to handle a list of uris to attach. - * @return the total size of all successfully attached files. + * @return true if anything has been attached. */ - private long handleAttachmentUrisFromIntent(List<Uri> uris) { + private boolean handleAttachmentUrisFromIntent(List<Uri> uris) { ArrayList<Attachment> attachments = Lists.newArrayList(); for (Uri uri : uris) { try { @@ -2023,8 +2025,8 @@ public class ComposeActivity extends ActionBarActivity return; } - final long size = handleAttachmentUrisFromIntent(Arrays.asList(contentUri)); - if (size > 0) { + final boolean attached = handleAttachmentUrisFromIntent(Arrays.asList(contentUri)); + if (attached) { mAttachmentsChanged = true; updateSaveUi(); } @@ -2042,11 +2044,9 @@ public class ComposeActivity extends ActionBarActivity private void addAttachmentAndUpdateView(Attachment attachment) { try { - long size = mAttachmentsView.addAttachment(mAccount, attachment); - if (size > 0) { - mAttachmentsChanged = true; - updateSaveUi(); - } + mAttachmentsView.addAttachment(mAccount, attachment); + mAttachmentsChanged = true; + updateSaveUi(); } catch (AttachmentFailureException e) { LogUtils.e(LOG_TAG, e, "Error adding attachment"); showAttachmentTooBigToast(e.getErrorRes()); @@ -2605,7 +2605,7 @@ public class ComposeActivity extends ActionBarActivity if (openedFds != null) { final Set<String> keys = openedFds.keySet(); for (final String key : keys) { - final ParcelFileDescriptor fd = openedFds.getParcelable(key); + final AssetFileDescriptor fd = openedFds.getParcelable(key); if (fd != null) { try { fd.close(); @@ -2715,9 +2715,16 @@ public class ComposeActivity extends ActionBarActivity continue; } - ParcelFileDescriptor fileDescriptor; + AssetFileDescriptor fileDescriptor; try { - fileDescriptor = resolver.openFileDescriptor(attachment.contentUri, "r"); + if (attachment.virtualMimeType == null) { + fileDescriptor = new AssetFileDescriptor( + resolver.openFileDescriptor(attachment.contentUri, "r"), 0, + AssetFileDescriptor.UNKNOWN_LENGTH); + } else { + fileDescriptor = resolver.openTypedAssetFileDescriptor( + attachment.contentUri, attachment.virtualMimeType, null, null); + } } catch (FileNotFoundException e) { LogUtils.e(LOG_TAG, e, "Exception attempting to open attachment"); fileDescriptor = null; @@ -3424,7 +3431,6 @@ public class ComposeActivity extends ActionBarActivity @SuppressLint("NewApi") private void doAttach(String type) { Intent i = new Intent(Intent.ACTION_GET_CONTENT); - i.addCategory(Intent.CATEGORY_OPENABLE); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); i.setType(type); diff --git a/src/com/android/mail/providers/Attachment.java b/src/com/android/mail/providers/Attachment.java index b8e86edeb..4ed33f83e 100644 --- a/src/com/android/mail/providers/Attachment.java +++ b/src/com/android/mail/providers/Attachment.java @@ -160,6 +160,14 @@ public class Attachment implements Parcelable { */ public String providerData; + /** + * Streamable mime type of the attachment in case it's a virtual file. + * + * Might be null. If null, then the default type (contentType) is assumed + * to be streamable. + */ + public String virtualMimeType; + private transient Uri mIdentifierUri; /** @@ -186,6 +194,7 @@ public class Attachment implements Parcelable { supportsDownloadAgain = in.readInt() == 1; type = in.readInt(); flags = in.readInt(); + virtualMimeType = in.readString(); } public Attachment(Cursor cursor) { @@ -211,6 +220,7 @@ public class Attachment implements Parcelable { cursor.getColumnIndex(AttachmentColumns.SUPPORTS_DOWNLOAD_AGAIN)) == 1; type = cursor.getInt(cursor.getColumnIndex(AttachmentColumns.TYPE)); flags = cursor.getInt(cursor.getColumnIndex(AttachmentColumns.FLAGS)); + virtualMimeType = cursor.getString(cursor.getColumnIndex(AttachmentColumns.VIRTUAL_MIME_TYPE)); } public Attachment(JSONObject srcJson) { @@ -228,6 +238,7 @@ public class Attachment implements Parcelable { supportsDownloadAgain = srcJson.optBoolean(AttachmentColumns.SUPPORTS_DOWNLOAD_AGAIN, true); type = srcJson.optInt(AttachmentColumns.TYPE); flags = srcJson.optInt(AttachmentColumns.FLAGS); + virtualMimeType = srcJson.optString(AttachmentColumns.VIRTUAL_MIME_TYPE, null); } /** @@ -257,6 +268,7 @@ public class Attachment implements Parcelable { type = inline ? AttachmentType.INLINE_CURRENT_MESSAGE : AttachmentType.STANDARD; partId = cid; flags = 0; + virtualMimeType = null; // insert attachment into content provider so that we can open the file final ContentResolver resolver = context.getContentResolver(); @@ -303,6 +315,7 @@ public class Attachment implements Parcelable { type = values.getAsInteger(AttachmentColumns.TYPE); flags = values.getAsInteger(AttachmentColumns.FLAGS); partId = values.getAsString(AttachmentColumns.CONTENT_ID); + virtualMimeType = values.getAsString(AttachmentColumns.VIRTUAL_MIME_TYPE); } /** @@ -328,6 +341,7 @@ public class Attachment implements Parcelable { values.put(AttachmentColumns.TYPE, type); values.put(AttachmentColumns.FLAGS, flags); values.put(AttachmentColumns.CONTENT_ID, partId); + values.put(AttachmentColumns.VIRTUAL_MIME_TYPE, virtualMimeType); return values; } @@ -348,6 +362,7 @@ public class Attachment implements Parcelable { dest.writeInt(supportsDownloadAgain ? 1 : 0); dest.writeInt(type); dest.writeInt(flags); + dest.writeString(virtualMimeType); } public JSONObject toJSON() throws JSONException { @@ -367,6 +382,7 @@ public class Attachment implements Parcelable { result.put(AttachmentColumns.SUPPORTS_DOWNLOAD_AGAIN, supportsDownloadAgain); result.put(AttachmentColumns.TYPE, type); result.put(AttachmentColumns.FLAGS, flags); + result.put(AttachmentColumns.VIRTUAL_MIME_TYPE, virtualMimeType); return result; } diff --git a/src/com/android/mail/providers/UIProvider.java b/src/com/android/mail/providers/UIProvider.java index 783e4f348..3bb6da54a 100644 --- a/src/com/android/mail/providers/UIProvider.java +++ b/src/com/android/mail/providers/UIProvider.java @@ -1958,7 +1958,8 @@ public class UIProvider { AttachmentColumns.SUPPORTS_DOWNLOAD_AGAIN, AttachmentColumns.TYPE, AttachmentColumns.FLAGS, - AttachmentColumns.CONTENT_ID + AttachmentColumns.CONTENT_ID, + AttachmentColumns.VIRTUAL_MIME_TYPE }; public static final int ATTACHMENT_NAME_COLUMN = 0; public static final int ATTACHMENT_SIZE_COLUMN = 1; @@ -1974,6 +1975,7 @@ public class UIProvider { public static final int ATTACHMENT_TYPE_COLUMN = 11; public static final int ATTACHMENT_FLAGS_COLUMN = 12; public static final int ATTACHMENT_CONTENT_ID_COLUMN = 13; + public static final int ATTACHMENT_VIRTUAL_MIME_TYPE_COLUMN = 14; /** Separates attachment info parts in strings in the database. */ public static final String ATTACHMENT_INFO_SEPARATOR = "\n"; // use to join @@ -2160,6 +2162,11 @@ public class UIProvider { */ public static final String CONTENT_ID = "contentId"; + /** + * Holds a streamable mime type for this attachment if it's a virtual file. + */ + public static final String VIRTUAL_MIME_TYPE = "streamableMimeType"; + private AttachmentColumns() {} } diff --git a/src/com/android/mail/utils/AttachmentUtils.java b/src/com/android/mail/utils/AttachmentUtils.java index 4837f7a02..8cdaef833 100644 --- a/src/com/android/mail/utils/AttachmentUtils.java +++ b/src/com/android/mail/utils/AttachmentUtils.java @@ -17,6 +17,8 @@ package com.android.mail.utils; import android.app.DownloadManager; import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetFileDescriptor.AutoCloseInputStream; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; @@ -187,13 +189,13 @@ public class AttachmentUtils { try { final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-kk:mm:ss"); file = File.createTempFile(dateFormat.format(new Date()), ".attachment", cacheDir); - final ParcelFileDescriptor fileDescriptor = attachmentFds != null - && attachment.contentUri != null ? (ParcelFileDescriptor) attachmentFds + final AssetFileDescriptor fileDescriptor = attachmentFds != null + && attachment.contentUri != null ? (AssetFileDescriptor) attachmentFds .getParcelable(attachment.contentUri.toString()) : null; if (fileDescriptor != null) { // Get the input stream from the file descriptor - inputStream = new FileInputStream(fileDescriptor.getFileDescriptor()); + inputStream = new AutoCloseInputStream(fileDescriptor); } else { if (attachment.contentUri == null) { // The contentUri of the attachment is null. This can happen when sending a @@ -203,7 +205,15 @@ public class AttachmentUtils { throw new FileNotFoundException("Missing contentUri in attachment"); } // Attempt to open the file - inputStream = context.getContentResolver().openInputStream(attachment.contentUri); + if (attachment.virtualMimeType == null) { + inputStream = context.getContentResolver().openInputStream(attachment.contentUri); + } else { + AssetFileDescriptor fd = context.getContentResolver().openTypedAssetFileDescriptor( + attachment.contentUri, attachment.virtualMimeType, null, null); + if (fd != null) { + inputStream = new AutoCloseInputStream(fd); + } + } } outputStream = new FileOutputStream(file); final long now = SystemClock.elapsedRealtime(); diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java index 95e8ee6ce..4efbccc03 100644 --- a/src/com/android/mail/utils/Utils.java +++ b/src/com/android/mail/utils/Utils.java @@ -145,6 +145,10 @@ public class Utils { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; } + public static boolean isRunningNOrLater() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; + } + /** * @return Whether we are running on a low memory device. This is used to disable certain * memory intensive features in the app. |