From 2cb31a2fac83602d9524432ce3e7dc0615eb0b2a Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 18 Jan 2012 06:00:26 +0100 Subject: [PATCH 1/6] Added button to show unnamed and inline attachments --- res/values-de/strings.xml | 2 + .../fsck/k9/mail/internet/MimeUtility.java | 11 +++++ src/com/fsck/k9/view/AttachmentView.java | 40 +++++++++++++++++-- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 03267f2a2..e6e26aae5 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -285,6 +285,8 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü Anhang konnte nicht auf SD-Karte gespeichert werden. Wählen Sie \"Bilder anzeigen\", um eingebettete Bilder abzurufen. Bilder anzeigen + Zeige Anhänge + Mehr… Lade Anhang. Es wurde kein Anzeigeprogramm für %s gefunden. diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index 5fd3c047d..405243d4b 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -2040,6 +2040,17 @@ public class MimeUtility { return DEFAULT_ATTACHMENT_MIME_TYPE; } + public static String getExtensionByMimeType(String mimeType) { + String lowerCaseMimeType = mimeType.toLowerCase(Locale.US); + for (String[] contentTypeMapEntry : MIME_TYPE_BY_EXTENSION_MAP) { + if (contentTypeMapEntry[1].equals(lowerCaseMimeType)) { + return contentTypeMapEntry[0]; + } + } + + return null; + } + /** * Convert some wrong MIME types encountered in the wild to canonical MIME types. * diff --git a/src/com/fsck/k9/view/AttachmentView.java b/src/com/fsck/k9/view/AttachmentView.java index f581c3db2..316668a4f 100644 --- a/src/com/fsck/k9/view/AttachmentView.java +++ b/src/com/fsck/k9/view/AttachmentView.java @@ -34,6 +34,7 @@ import com.fsck.k9.helper.SizeFormatter; import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Part; +import com.fsck.k9.mail.internet.MimeHeader; import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart; import com.fsck.k9.provider.AttachmentProvider; @@ -101,7 +102,28 @@ public class AttachmentView extends FrameLayout { */ public void showFileBrowser(AttachmentView caller); } - public boolean populateFromPart(Part inputPart, Message message, Account account, MessagingController controller, MessagingListener listener) { + + /** + * Populates this view with information about the attachment. + * + *

+ * This method also decides which attachments are displayed when the "show attachments" button + * is pressed, and which attachments are only displayed after the "show more attachments" + * button was pressed.
+ * Inline attachments with content ID and unnamed attachments fall into the second category. + *

+ * + * @param inputPart + * @param message + * @param account + * @param controller + * @param listener + * + * @return {@code true} for a regular attachment. {@code false}, otherwise. + */ + public boolean populateFromPart(Part inputPart, Message message, Account account, + MessagingController controller, MessagingListener listener) { + boolean firstClassAttachment = true; try { part = (LocalAttachmentBodyPart) inputPart; @@ -112,8 +134,20 @@ public class AttachmentView extends FrameLayout { if (name == null) { name = MimeUtility.getHeaderParameter(contentDisposition, "filename"); } + if (name == null) { - return false; + firstClassAttachment = false; + String extension = MimeUtility.getExtensionByMimeType(contentType); + name = "noname" + ((extension != null) ? "." + extension : ""); + } + + // Inline parts with a content-id are almost certainly components of an HTML message + // not attachments. Only show them if the user pressed the button to show more + // attachments. + if (contentDisposition != null && + MimeUtility.getHeaderParameter(contentDisposition, null).matches("^(?i:inline)") + && part.getHeader(MimeHeader.HEADER_CONTENT_ID) != null) { + firstClassAttachment = false; } mAccount = account; @@ -180,7 +214,7 @@ public class AttachmentView extends FrameLayout { Log.e(K9.LOG_TAG, "error ", e); } - return true; + return firstClassAttachment; } private Bitmap getPreviewIcon() { From 25dff5ae6a5668bf51a5033be0906e1167639bb9 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 27 Feb 2012 20:29:22 +0100 Subject: [PATCH 2/6] Don't choke on invalid size parameters for attachment parts --- src/com/fsck/k9/view/AttachmentView.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/com/fsck/k9/view/AttachmentView.java b/src/com/fsck/k9/view/AttachmentView.java index 316668a4f..72f2b8cea 100644 --- a/src/com/fsck/k9/view/AttachmentView.java +++ b/src/com/fsck/k9/view/AttachmentView.java @@ -155,7 +155,13 @@ public class AttachmentView extends FrameLayout { mController = controller; mListener = listener; - size = Integer.parseInt(MimeUtility.getHeaderParameter(contentDisposition, "size")); + String sizeParam = MimeUtility.getHeaderParameter(contentDisposition, "size"); + if (sizeParam != null) { + try { + size = Integer.parseInt(sizeParam); + } catch (NumberFormatException e) { /* ignore */ } + } + contentType = MimeUtility.getMimeTypeForViewing(part.getMimeType(), name); TextView attachmentName = (TextView) findViewById(R.id.attachment_name); TextView attachmentInfo = (TextView) findViewById(R.id.attachment_info); From 206c559236bc34ac5de5e37a04ee912b193d1587 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 27 Feb 2012 21:00:44 +0100 Subject: [PATCH 3/6] Don't handle exceptions in AttachmentView.populateFromPart() We now catch exceptions in SingleMessageView.renderAttachments(). This way we can avoid adding AttachmentViews that couldn't be properly populated. --- src/com/fsck/k9/view/AttachmentView.java | 180 ++++++++++---------- src/com/fsck/k9/view/SingleMessageView.java | 12 +- 2 files changed, 97 insertions(+), 95 deletions(-) diff --git a/src/com/fsck/k9/view/AttachmentView.java b/src/com/fsck/k9/view/AttachmentView.java index 72f2b8cea..64ff1f08d 100644 --- a/src/com/fsck/k9/view/AttachmentView.java +++ b/src/com/fsck/k9/view/AttachmentView.java @@ -33,6 +33,7 @@ import com.fsck.k9.helper.MediaScannerNotifier; import com.fsck.k9.helper.SizeFormatter; import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.Message; +import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.Part; import com.fsck.k9.mail.internet.MimeHeader; import com.fsck.k9.mail.internet.MimeUtility; @@ -120,104 +121,101 @@ public class AttachmentView extends FrameLayout { * @param listener * * @return {@code true} for a regular attachment. {@code false}, otherwise. + * + * @throws MessagingException + * In case of an error */ public boolean populateFromPart(Part inputPart, Message message, Account account, - MessagingController controller, MessagingListener listener) { + MessagingController controller, MessagingListener listener) throws MessagingException { boolean firstClassAttachment = true; - try { - part = (LocalAttachmentBodyPart) inputPart; + part = (LocalAttachmentBodyPart) inputPart; - contentType = MimeUtility.unfoldAndDecode(part.getContentType()); - String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition()); + contentType = MimeUtility.unfoldAndDecode(part.getContentType()); + String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition()); - name = MimeUtility.getHeaderParameter(contentType, "name"); - if (name == null) { - name = MimeUtility.getHeaderParameter(contentDisposition, "filename"); - } - - if (name == null) { - firstClassAttachment = false; - String extension = MimeUtility.getExtensionByMimeType(contentType); - name = "noname" + ((extension != null) ? "." + extension : ""); - } - - // Inline parts with a content-id are almost certainly components of an HTML message - // not attachments. Only show them if the user pressed the button to show more - // attachments. - if (contentDisposition != null && - MimeUtility.getHeaderParameter(contentDisposition, null).matches("^(?i:inline)") - && part.getHeader(MimeHeader.HEADER_CONTENT_ID) != null) { - firstClassAttachment = false; - } - - mAccount = account; - mMessage = message; - mController = controller; - mListener = listener; - - String sizeParam = MimeUtility.getHeaderParameter(contentDisposition, "size"); - if (sizeParam != null) { - try { - size = Integer.parseInt(sizeParam); - } catch (NumberFormatException e) { /* ignore */ } - } - - contentType = MimeUtility.getMimeTypeForViewing(part.getMimeType(), name); - TextView attachmentName = (TextView) findViewById(R.id.attachment_name); - TextView attachmentInfo = (TextView) findViewById(R.id.attachment_info); - ImageView attachmentIcon = (ImageView) findViewById(R.id.attachment_icon); - viewButton = (Button) findViewById(R.id.view); - downloadButton = (Button) findViewById(R.id.download); - if ((!MimeUtility.mimeTypeMatches(contentType, K9.ACCEPTABLE_ATTACHMENT_VIEW_TYPES)) - || (MimeUtility.mimeTypeMatches(contentType, K9.UNACCEPTABLE_ATTACHMENT_VIEW_TYPES))) { - viewButton.setVisibility(View.GONE); - } - if ((!MimeUtility.mimeTypeMatches(contentType, K9.ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES)) - || (MimeUtility.mimeTypeMatches(contentType, K9.UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))) { - downloadButton.setVisibility(View.GONE); - } - if (size > K9.MAX_ATTACHMENT_DOWNLOAD_SIZE) { - viewButton.setVisibility(View.GONE); - downloadButton.setVisibility(View.GONE); - } - - viewButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - onViewButtonClicked(); - return; - } - }); - - - downloadButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - onSaveButtonClicked(); - return; - } - }); - downloadButton.setOnLongClickListener(new OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - callback.showFileBrowser(AttachmentView.this); - return true; - } - }); - - attachmentName.setText(name); - attachmentInfo.setText(SizeFormatter.formatSize(mContext, size)); - Bitmap previewIcon = getPreviewIcon(); - if (previewIcon != null) { - attachmentIcon.setImageBitmap(previewIcon); - } else { - attachmentIcon.setImageResource(R.drawable.attached_image_placeholder); - } + name = MimeUtility.getHeaderParameter(contentType, "name"); + if (name == null) { + name = MimeUtility.getHeaderParameter(contentDisposition, "filename"); } - catch (Exception e) { - Log.e(K9.LOG_TAG, "error ", e); + if (name == null) { + firstClassAttachment = false; + String extension = MimeUtility.getExtensionByMimeType(contentType); + name = "noname" + ((extension != null) ? "." + extension : ""); + } + + // Inline parts with a content-id are almost certainly components of an HTML message + // not attachments. Only show them if the user pressed the button to show more + // attachments. + if (contentDisposition != null && + MimeUtility.getHeaderParameter(contentDisposition, null).matches("^(?i:inline)") + && part.getHeader(MimeHeader.HEADER_CONTENT_ID) != null) { + firstClassAttachment = false; + } + + mAccount = account; + mMessage = message; + mController = controller; + mListener = listener; + + String sizeParam = MimeUtility.getHeaderParameter(contentDisposition, "size"); + if (sizeParam != null) { + try { + size = Integer.parseInt(sizeParam); + } catch (NumberFormatException e) { /* ignore */ } + } + + contentType = MimeUtility.getMimeTypeForViewing(part.getMimeType(), name); + TextView attachmentName = (TextView) findViewById(R.id.attachment_name); + TextView attachmentInfo = (TextView) findViewById(R.id.attachment_info); + ImageView attachmentIcon = (ImageView) findViewById(R.id.attachment_icon); + viewButton = (Button) findViewById(R.id.view); + downloadButton = (Button) findViewById(R.id.download); + if ((!MimeUtility.mimeTypeMatches(contentType, K9.ACCEPTABLE_ATTACHMENT_VIEW_TYPES)) + || (MimeUtility.mimeTypeMatches(contentType, K9.UNACCEPTABLE_ATTACHMENT_VIEW_TYPES))) { + viewButton.setVisibility(View.GONE); + } + if ((!MimeUtility.mimeTypeMatches(contentType, K9.ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES)) + || (MimeUtility.mimeTypeMatches(contentType, K9.UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES))) { + downloadButton.setVisibility(View.GONE); + } + if (size > K9.MAX_ATTACHMENT_DOWNLOAD_SIZE) { + viewButton.setVisibility(View.GONE); + downloadButton.setVisibility(View.GONE); + } + + viewButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onViewButtonClicked(); + return; + } + }); + + + downloadButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onSaveButtonClicked(); + return; + } + }); + downloadButton.setOnLongClickListener(new OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + callback.showFileBrowser(AttachmentView.this); + return true; + } + }); + + attachmentName.setText(name); + attachmentInfo.setText(SizeFormatter.formatSize(mContext, size)); + Bitmap previewIcon = getPreviewIcon(); + if (previewIcon != null) { + attachmentIcon.setImageBitmap(previewIcon); + } else { + attachmentIcon.setImageResource(R.drawable.attached_image_placeholder); } return firstClassAttachment; diff --git a/src/com/fsck/k9/view/SingleMessageView.java b/src/com/fsck/k9/view/SingleMessageView.java index f9b2255ad..b1e0980e9 100644 --- a/src/com/fsck/k9/view/SingleMessageView.java +++ b/src/com/fsck/k9/view/SingleMessageView.java @@ -398,10 +398,14 @@ public class SingleMessageView extends LinearLayout implements OnClickListener { AttachmentView view = (AttachmentView)mInflater.inflate(R.layout.message_view_attachment, null); view.setCallback(attachmentCallback); - if (view.populateFromPart(part, message, account, controller, listener)) { - addAttachment(view); - } else { - addHiddenAttachment(view); + try { + if (view.populateFromPart(part, message, account, controller, listener)) { + addAttachment(view); + } else { + addHiddenAttachment(view); + } + } catch (Exception e) { + Log.e(K9.LOG_TAG, "Error adding attachment view", e); } } } From ba979808fef2db618f8b0f79c90190ecf2b0aefd Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 27 Feb 2012 21:34:18 +0100 Subject: [PATCH 4/6] Use AttachmentView instance as onClick and onLongClick listener --- src/com/fsck/k9/view/AttachmentView.java | 55 +++++++++++++----------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/com/fsck/k9/view/AttachmentView.java b/src/com/fsck/k9/view/AttachmentView.java index 64ff1f08d..5eccda9eb 100644 --- a/src/com/fsck/k9/view/AttachmentView.java +++ b/src/com/fsck/k9/view/AttachmentView.java @@ -18,6 +18,8 @@ import android.os.Environment; import android.util.AttributeSet; import android.util.Log; import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageView; @@ -40,7 +42,7 @@ import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart; import com.fsck.k9.provider.AttachmentProvider; -public class AttachmentView extends FrameLayout { +public class AttachmentView extends FrameLayout implements OnClickListener, OnLongClickListener { /** * Regular expression that represents characters we won't allow in file names. * @@ -184,30 +186,9 @@ public class AttachmentView extends FrameLayout { downloadButton.setVisibility(View.GONE); } - viewButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - onViewButtonClicked(); - return; - } - }); - - - downloadButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - onSaveButtonClicked(); - return; - } - }); - downloadButton.setOnLongClickListener(new OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - callback.showFileBrowser(AttachmentView.this); - return true; - } - }); + viewButton.setOnClickListener(this); + downloadButton.setOnClickListener(this); + downloadButton.setOnLongClickListener(this); attachmentName.setText(name); attachmentInfo.setText(SizeFormatter.formatSize(mContext, size)); @@ -221,6 +202,30 @@ public class AttachmentView extends FrameLayout { return firstClassAttachment; } + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.view: { + onViewButtonClicked(); + break; + } + case R.id.download: { + onSaveButtonClicked(); + break; + } + } + } + + @Override + public boolean onLongClick(View view) { + if (view.getId() == R.id.download) { + callback.showFileBrowser(this); + return true; + } + + return false; + } + private Bitmap getPreviewIcon() { try { return BitmapFactory.decodeStream( From e72afc1641e3e5fa20d4108581f96676939cd07f Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 27 Feb 2012 23:20:30 +0100 Subject: [PATCH 5/6] Set content type of attachment even when no file name was found --- src/com/fsck/k9/mail/store/LocalStore.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index fe830ff7b..7a5c13472 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -1692,6 +1692,12 @@ public class LocalStore extends Store implements Serializable { contentDisposition, name, // TODO: Should use encoded word defined in RFC 2231. size)); + } else { + bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE, type); + bp.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, + String.format("%s;\n size=%d", + contentDisposition, + size)); } bp.setHeader(MimeHeader.HEADER_CONTENT_ID, contentId); From ae6679769b76b7d060dfff1478178b5a049399ff Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Tue, 28 Feb 2012 09:05:46 -0500 Subject: [PATCH 6/6] Bumped manifest to 4.112 --- AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8f5db1db2..fa40d48d5 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@