diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8f5db1db2..fa40d48d5 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ 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/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index 20b6c1bbe..5b26777c8 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -1866,6 +1866,12 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ? 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); diff --git a/src/com/fsck/k9/view/AttachmentView.java b/src/com/fsck/k9/view/AttachmentView.java index f581c3db2..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; @@ -33,12 +35,14 @@ 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; 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. * @@ -101,86 +105,125 @@ public class AttachmentView extends FrameLayout { */ public void showFileBrowser(AttachmentView caller); } - public boolean populateFromPart(Part inputPart, Message message, Account account, MessagingController controller, MessagingListener listener) { - try { - part = (LocalAttachmentBodyPart) inputPart; - contentType = MimeUtility.unfoldAndDecode(part.getContentType()); - String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition()); + /** + * 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. + * + * @throws MessagingException + * In case of an error + */ + public boolean populateFromPart(Part inputPart, Message message, Account account, + MessagingController controller, MessagingListener listener) throws MessagingException { + boolean firstClassAttachment = true; + part = (LocalAttachmentBodyPart) inputPart; - name = MimeUtility.getHeaderParameter(contentType, "name"); - if (name == null) { - name = MimeUtility.getHeaderParameter(contentDisposition, "filename"); - } - if (name == null) { - return false; - } + contentType = MimeUtility.unfoldAndDecode(part.getContentType()); + String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition()); - mAccount = account; - mMessage = message; - mController = controller; - mListener = listener; - - size = Integer.parseInt(MimeUtility.getHeaderParameter(contentDisposition, "size")); - 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 : ""); } - return true; + // 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(this); + downloadButton.setOnClickListener(this); + downloadButton.setOnLongClickListener(this); + + 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; + } + + @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() { 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); } } }