diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/AttachmentViewInfo.java b/k9mail/src/main/java/com/fsck/k9/mailstore/AttachmentViewInfo.java new file mode 100644 index 000000000..8b0bb3050 --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/AttachmentViewInfo.java @@ -0,0 +1,26 @@ +package com.fsck.k9.mailstore; + + +import android.net.Uri; + +import com.fsck.k9.mail.Part; + + +public class AttachmentViewInfo { + public final String mimeType; + public final String displayName; + public final long size; + public final Uri uri; + public final boolean firstClassAttachment; + public final Part part; + + public AttachmentViewInfo(String mimeType, String displayName, long size, Uri uri, boolean firstClassAttachment, + Part part) { + this.mimeType = mimeType; + this.displayName = displayName; + this.size = size; + this.uri = uri; + this.firstClassAttachment = firstClassAttachment; + this.part = part; + } +} diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java b/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java index 6a4b61170..fc867b61a 100644 --- a/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java @@ -1,6 +1,7 @@ package com.fsck.k9.mailstore; import android.content.Context; +import android.net.Uri; import com.fsck.k9.R; import com.fsck.k9.mail.Address; @@ -11,7 +12,9 @@ import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.Part; import com.fsck.k9.helper.HtmlConverter; import com.fsck.k9.mail.internet.MessageExtractor; +import com.fsck.k9.mail.internet.MimeHeader; import com.fsck.k9.mail.internet.MimeMultipart; +import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.internet.Viewable; import java.util.ArrayList; @@ -118,35 +121,6 @@ public class LocalMessageExtractor { } } - public static ViewableContainer extractPartsFromDraft(Message message) - throws MessagingException { - - Body body = message.getBody(); - if (message.isMimeType("multipart/mixed") && body instanceof MimeMultipart) { - MimeMultipart multipart = (MimeMultipart) body; - - ViewableContainer container; - int count = multipart.getCount(); - if (count >= 1) { - // The first part is either a text/plain or a multipart/alternative - BodyPart firstPart = multipart.getBodyPart(0); - container = extractTextual(firstPart); - - // The rest should be attachments - for (int i = 1; i < count; i++) { - BodyPart bodyPart = multipart.getBodyPart(i); - container.attachments.add(bodyPart); - } - } else { - container = new ViewableContainer("", "", new ArrayList()); - } - - return container; - } - - return extractTextual(message); - } - /** * Use the contents of a {@link com.fsck.k9.mail.internet.Viewable} to create the HTML to be displayed. * @@ -471,6 +445,58 @@ public class LocalMessageExtractor { public static MessageViewInfo decodeMessageForView(Context context, Message message) throws MessagingException { //TODO: Modify extractTextAndAttachments() to only extract the text type (plain vs. HTML) we currently need. ViewableContainer viewable = LocalMessageExtractor.extractTextAndAttachments(context, message); - return new MessageViewInfo(viewable.html, viewable.attachments, message); + List attachments = extractAttachmentInfos(viewable.attachments); + return new MessageViewInfo(viewable.html, attachments, message); + } + + private static List extractAttachmentInfos(List attachmentParts) + throws MessagingException { + + List attachments = new ArrayList(); + for (Part part : attachmentParts) { + attachments.add(extractAttachmentInfo(part)); + } + + return attachments; + } + + private static AttachmentViewInfo extractAttachmentInfo(Part part) throws MessagingException { + boolean firstClassAttachment = true; + + String mimeType = part.getMimeType(); + String contentTypeHeader = MimeUtility.unfoldAndDecode(part.getContentType()); + String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition()); + + String name = MimeUtility.getHeaderParameter(contentDisposition, "filename"); + if (name == null) { + name = MimeUtility.getHeaderParameter(contentTypeHeader, "name"); + } + + if (name == null) { + firstClassAttachment = false; + String extension = MimeUtility.getExtensionByMimeType(mimeType); + 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; + } + + long size = 0; + String sizeParam = MimeUtility.getHeaderParameter(contentDisposition, "size"); + if (sizeParam != null) { + try { + size = Integer.parseInt(sizeParam); + } catch (NumberFormatException e) { /* ignore */ } + } + + Uri uri = Uri.parse("dummy://this.needs.fixing"); //FIXME + + return new AttachmentViewInfo(mimeType, name, size, uri, firstClassAttachment, part); } } diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfo.java b/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfo.java index 6e359bbbc..98f8fc2b2 100644 --- a/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfo.java +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfo.java @@ -5,15 +5,14 @@ import java.util.Collections; import java.util.List; import com.fsck.k9.mail.Message; -import com.fsck.k9.mail.Part; public class MessageViewInfo { public final String text; - public final List attachments; + public final List attachments; public final Message message; - public MessageViewInfo(String text, List attachments, Message message) { + public MessageViewInfo(String text, List attachments, Message message) { this.text = text; this.attachments = Collections.unmodifiableList(attachments); this.message = message; diff --git a/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java b/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java new file mode 100644 index 000000000..fe8cd0f8a --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java @@ -0,0 +1,259 @@ +package com.fsck.k9.ui.messageview; + + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Environment; +import android.util.Log; +import android.widget.Toast; + +import com.fsck.k9.K9; +import com.fsck.k9.R; +import com.fsck.k9.cache.TemporaryAttachmentStore; +import com.fsck.k9.helper.FileHelper; +import com.fsck.k9.helper.MediaScannerNotifier; +import com.fsck.k9.mail.internet.MimeUtility; +import com.fsck.k9.mailstore.AttachmentViewInfo; +import org.apache.commons.io.IOUtils; + + +public class AttachmentController { + private final Context context; + private final SingleMessageView messageView; + private final AttachmentViewInfo attachment; + + AttachmentController(SingleMessageView messageView, AttachmentViewInfo attachment) { + this.context = messageView.getContext(); + this.messageView = messageView; + this.attachment = attachment; + } + + public void viewAttachment() { + new ViewAttachmentAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void saveAttachment() { + saveAttachmentTo(K9.getAttachmentDefaultPath()); + } + + public void saveAttachmentTo(String directory) { + saveAttachmentTo(new File(directory)); + } + + private void saveAttachmentTo(File directory) { + boolean isExternalStorageMounted = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); + if (!isExternalStorageMounted) { + String message = context.getString(R.string.message_view_status_attachment_not_saved); + displayMessageToUser(message); + return; + } + + //FIXME: write file in background thread + try { + File file = saveAttachmentWithUniqueFileName(directory); + + displayAttachmentSavedMessage(file.toString()); + + MediaScannerNotifier.notify(context, file); + } catch (IOException ioe) { + if (K9.DEBUG) { + Log.e(K9.LOG_TAG, "Error saving attachment", ioe); + } + displayAttachmentNotSavedMessage(); + } + } + + private File saveAttachmentWithUniqueFileName(File directory) throws IOException { + String filename = FileHelper.sanitizeFilename(attachment.displayName); + File file = FileHelper.createUniqueFile(directory, filename); + + writeAttachmentToStorage(file); + + return file; + } + + private void writeAttachmentToStorage(File file) throws IOException { + InputStream in = context.getContentResolver().openInputStream(attachment.uri); + try { + OutputStream out = new FileOutputStream(file); + try { + IOUtils.copy(in, out); + out.flush(); + } finally { + out.close(); + } + } finally { + in.close(); + } + } + + private Intent getBestViewIntentAndSaveFileIfNecessary() { + String displayName = attachment.displayName; + String inferredMimeType = MimeUtility.getMimeTypeByExtension(displayName); + + IntentAndResolvedActivitiesCount resolvedIntentInfo; + String mimeType = attachment.mimeType; + if (MimeUtility.isDefaultMimeType(mimeType)) { + resolvedIntentInfo = getBestViewIntentForMimeType(inferredMimeType); + } else { + resolvedIntentInfo = getBestViewIntentForMimeType(mimeType); + if (!resolvedIntentInfo.hasResolvedActivities() && !inferredMimeType.equals(mimeType)) { + resolvedIntentInfo = getBestViewIntentForMimeType(inferredMimeType); + } + } + + if (!resolvedIntentInfo.hasResolvedActivities()) { + resolvedIntentInfo = getBestViewIntentForMimeType(MimeUtility.DEFAULT_ATTACHMENT_MIME_TYPE); + } + + Intent viewIntent; + if (resolvedIntentInfo.hasResolvedActivities() && resolvedIntentInfo.containsFileUri()) { + try { + File tempFile = TemporaryAttachmentStore.getFileForWriting(context, displayName); + writeAttachmentToStorage(tempFile); + viewIntent = createViewIntentForFileUri(resolvedIntentInfo.getMimeType(), Uri.fromFile(tempFile)); + } catch (IOException e) { + if (K9.DEBUG) { + Log.e(K9.LOG_TAG, "Error while saving attachment to use file:// URI with ACTION_VIEW Intent", e); + } + viewIntent = createViewIntentForAttachmentProviderUri(MimeUtility.DEFAULT_ATTACHMENT_MIME_TYPE); + } + } else { + viewIntent = resolvedIntentInfo.getIntent(); + } + + return viewIntent; + } + + private IntentAndResolvedActivitiesCount getBestViewIntentForMimeType(String mimeType) { + Intent contentUriIntent = createViewIntentForAttachmentProviderUri(mimeType); + int contentUriActivitiesCount = getResolvedIntentActivitiesCount(contentUriIntent); + + if (contentUriActivitiesCount > 0) { + return new IntentAndResolvedActivitiesCount(contentUriIntent, contentUriActivitiesCount); + } + + File tempFile = TemporaryAttachmentStore.getFile(context, attachment.displayName); + Uri tempFileUri = Uri.fromFile(tempFile); + Intent fileUriIntent = createViewIntentForFileUri(mimeType, tempFileUri); + int fileUriActivitiesCount = getResolvedIntentActivitiesCount(fileUriIntent); + + if (fileUriActivitiesCount > 0) { + return new IntentAndResolvedActivitiesCount(fileUriIntent, fileUriActivitiesCount); + } + + return new IntentAndResolvedActivitiesCount(contentUriIntent, contentUriActivitiesCount); + } + + private Intent createViewIntentForAttachmentProviderUri(String mimeType) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(attachment.uri, mimeType); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + addUiIntentFlags(intent); + + return intent; + } + + private Intent createViewIntentForFileUri(String mimeType, Uri uri) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(uri, mimeType); + addUiIntentFlags(intent); + + return intent; + } + + private void addUiIntentFlags(Intent intent) { + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + } + + private int getResolvedIntentActivitiesCount(Intent intent) { + PackageManager packageManager = context.getPackageManager(); + + List resolveInfos = + packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + + return resolveInfos.size(); + } + + private void displayAttachmentSavedMessage(final String filename) { + String message = context.getString(R.string.message_view_status_attachment_saved, filename); + displayMessageToUser(message); + } + + private void displayAttachmentNotSavedMessage() { + String message = context.getString(R.string.message_view_status_attachment_not_saved); + displayMessageToUser(message); + } + + private void displayMessageToUser(String message) { + Toast.makeText(context, message, Toast.LENGTH_LONG).show(); + } + + private static class IntentAndResolvedActivitiesCount { + private Intent intent; + private int activitiesCount; + + IntentAndResolvedActivitiesCount(Intent intent, int activitiesCount) { + this.intent = intent; + this.activitiesCount = activitiesCount; + } + + public Intent getIntent() { + return intent; + } + + public boolean hasResolvedActivities() { + return activitiesCount > 0; + } + + public String getMimeType() { + return intent.getType(); + } + + public boolean containsFileUri() { + return "file".equals(intent.getData().getScheme()); + } + } + + private class ViewAttachmentAsyncTask extends AsyncTask { + + @Override + protected void onPreExecute() { + messageView.disableAttachmentViewButton(attachment); + } + + @Override + protected Intent doInBackground(Void... params) { + return getBestViewIntentAndSaveFileIfNecessary(); + } + + @Override + protected void onPostExecute(Intent intent) { + viewAttachment(intent); + messageView.enableAttachmentViewButton(attachment); + } + + private void viewAttachment(Intent intent) { + try { + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(K9.LOG_TAG, "Could not display attachment of type " + attachment.mimeType, e); + + String message = context.getString(R.string.message_view_no_viewer, attachment.mimeType); + displayMessageToUser(message); + } + } + } +} diff --git a/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentView.java b/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentView.java index 250c3ca13..ac2acb59b 100644 --- a/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentView.java +++ b/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentView.java @@ -1,21 +1,10 @@ package com.fsck.k9.ui.messageview; -import java.io.File; -import java.io.IOException; -import java.util.List; - -import android.content.ActivityNotFoundException; import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.graphics.Bitmap; -import android.net.Uri; import android.os.AsyncTask; -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; @@ -23,124 +12,50 @@ import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; -import android.widget.Toast; -import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.R; -import com.fsck.k9.cache.TemporaryAttachmentStore; -import com.fsck.k9.controller.MessagingController; -import com.fsck.k9.controller.MessagingListener; -import com.fsck.k9.helper.FileHelper; -import com.fsck.k9.helper.MediaScannerNotifier; import com.fsck.k9.helper.SizeFormatter; -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.mailstore.AttachmentViewInfo; public class AttachmentView extends FrameLayout implements OnClickListener, OnLongClickListener { - private Context context; - private Message message; - private Part part; - private Account account; - private MessagingController controller; - private MessagingListener listener; - private AttachmentFileDownloadCallback callback; + private AttachmentViewInfo attachment; + private AttachmentViewCallback callback; private Button viewButton; private Button downloadButton; - private String name; - private String contentType; - private long size; - public AttachmentView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - this.context = context; } public AttachmentView(Context context, AttributeSet attrs) { super(context, attrs); - this.context = context; } public AttachmentView(Context context) { super(context); - this.context = context; } - public void setButtonsEnabled(boolean enabled) { - viewButton.setEnabled(enabled); - downloadButton.setEnabled(enabled); + public AttachmentViewInfo getAttachment() { + return attachment; } - /** - * 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. - *

- * - * @return {@code true} for a regular attachment. {@code false} for attachments that should be initially hidden. - */ - public boolean populateFromPart(Part part, Message message, Account account, - MessagingController controller, MessagingListener listener) throws MessagingException { + public void enableViewButton() { + viewButton.setEnabled(true); + } - this.part = part; - this.message = message; - this.account = account; - this.controller = controller; - this.listener = listener; + public void disableViewButton() { + viewButton.setEnabled(false); + } - boolean firstClassAttachment = extractAttachmentInformation(part); + public void setAttachment(AttachmentViewInfo attachment) throws MessagingException { + this.attachment = attachment; displayAttachmentInformation(); - - return firstClassAttachment; - } - - //TODO: extract this code to a helper class - private boolean extractAttachmentInformation(Part part) throws MessagingException { - boolean firstClassAttachment = true; - - contentType = part.getMimeType(); - String contentTypeHeader = MimeUtility.unfoldAndDecode(part.getContentType()); - String contentDisposition = MimeUtility.unfoldAndDecode(part.getDisposition()); - - name = MimeUtility.getHeaderParameter(contentTypeHeader, "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; - } - - String sizeParam = MimeUtility.getHeaderParameter(contentDisposition, "size"); - if (sizeParam != null) { - try { - size = Integer.parseInt(sizeParam); - } catch (NumberFormatException e) { /* ignore */ } - } - - return firstClassAttachment; } private void displayAttachmentInformation() { @@ -149,7 +64,7 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo viewButton = (Button) findViewById(R.id.view); downloadButton = (Button) findViewById(R.id.download); - if (size > K9.MAX_ATTACHMENT_DOWNLOAD_SIZE) { + if (attachment.size > K9.MAX_ATTACHMENT_DOWNLOAD_SIZE) { viewButton.setVisibility(View.GONE); downloadButton.setVisibility(View.GONE); } @@ -158,8 +73,8 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo downloadButton.setOnClickListener(this); downloadButton.setOnLongClickListener(this); - attachmentName.setText(name); - attachmentInfo.setText(SizeFormatter.formatSize(context, size)); + attachmentName.setText(attachment.displayName); + attachmentInfo.setText(SizeFormatter.formatSize(getContext(), attachment.size)); ImageView thumbnail = (ImageView) findViewById(R.id.attachment_icon); new LoadAndDisplayThumbnailAsyncTask(thumbnail).execute(); @@ -169,11 +84,11 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo public void onClick(View view) { switch (view.getId()) { case R.id.view: { - onViewButtonClicked(); + onViewButtonClick(); break; } case R.id.download: { - onSaveButtonClicked(); + onSaveButtonClick(); break; } } @@ -182,231 +97,29 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo @Override public boolean onLongClick(View view) { if (view.getId() == R.id.download) { - callback.pickDirectoryToSaveAttachmentTo(this); + onSaveButtonLongClick(); return true; } return false; } - private void onViewButtonClicked() { - if (message != null) { - controller.loadAttachment(account, message, part, new Object[] {false, this}, listener); - } + private void onViewButtonClick() { + callback.onViewAttachment(attachment); } - private void onSaveButtonClicked() { - boolean isExternalStorageMounted = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); - if (!isExternalStorageMounted) { - String message = context.getString(R.string.message_view_status_attachment_not_saved); - displayMessageToUser(message); - return; - } - - if (message != null) { - controller.loadAttachment(account, message, part, new Object[] {true, this}, listener); - } + private void onSaveButtonClick() { + callback.onSaveAttachment(attachment); } - public void writeFile() { - writeFile(new File(K9.getAttachmentDefaultPath())); + private void onSaveButtonLongClick() { + callback.onSaveAttachmentToUserProvidedDirectory(attachment); } - /** - * Saves the attachment as file in the given directory - */ - public void writeFile(File directory) { - try { - File file = saveAttachmentWithUniqueFileName(directory); - - displayAttachmentSavedMessage(file.toString()); - - MediaScannerNotifier.notify(context, file); - } catch (IOException ioe) { - if (K9.DEBUG) { - Log.e(K9.LOG_TAG, "Error saving attachment", ioe); - } - displayAttachmentNotSavedMessage(); - } - } - - private File saveAttachmentWithUniqueFileName(File directory) throws IOException { - String filename = FileHelper.sanitizeFilename(name); - File file = FileHelper.createUniqueFile(directory, filename); - - writeAttachmentToStorage(file); - - return file; - } - - private void writeAttachmentToStorage(File file) throws IOException { - //FIXME - throw new RuntimeException("temporarily disabled"); -// Uri uri = AttachmentProvider.getAttachmentUri(account, part.getAttachmentId()); -// InputStream in = context.getContentResolver().openInputStream(uri); -// try { -// OutputStream out = new FileOutputStream(file); -// try { -// IOUtils.copy(in, out); -// out.flush(); -// } finally { -// out.close(); -// } -// } finally { -// in.close(); -// } - } - - public void showFile() { - new ViewAttachmentAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private Intent getBestViewIntentAndSaveFileIfNecessary() { - String inferredMimeType = MimeUtility.getMimeTypeByExtension(name); - - IntentAndResolvedActivitiesCount resolvedIntentInfo; - if (MimeUtility.isDefaultMimeType(contentType)) { - resolvedIntentInfo = getBestViewIntentForMimeType(inferredMimeType); - } else { - resolvedIntentInfo = getBestViewIntentForMimeType(contentType); - if (!resolvedIntentInfo.hasResolvedActivities() && !inferredMimeType.equals(contentType)) { - resolvedIntentInfo = getBestViewIntentForMimeType(inferredMimeType); - } - } - - if (!resolvedIntentInfo.hasResolvedActivities()) { - resolvedIntentInfo = getBestViewIntentForMimeType(MimeUtility.DEFAULT_ATTACHMENT_MIME_TYPE); - } - - Intent viewIntent; - if (resolvedIntentInfo.hasResolvedActivities() && resolvedIntentInfo.containsFileUri()) { - try { - File tempFile = TemporaryAttachmentStore.getFileForWriting(context, name); - writeAttachmentToStorage(tempFile); - viewIntent = createViewIntentForFileUri(resolvedIntentInfo.getMimeType(), Uri.fromFile(tempFile)); - } catch (IOException e) { - if (K9.DEBUG) { - Log.e(K9.LOG_TAG, "Error while saving attachment to use file:// URI with ACTION_VIEW Intent", e); - } - viewIntent = createViewIntentForAttachmentProviderUri(MimeUtility.DEFAULT_ATTACHMENT_MIME_TYPE); - } - } else { - viewIntent = resolvedIntentInfo.getIntent(); - } - - return viewIntent; - } - - private IntentAndResolvedActivitiesCount getBestViewIntentForMimeType(String mimeType) { - Intent contentUriIntent = createViewIntentForAttachmentProviderUri(mimeType); - int contentUriActivitiesCount = getResolvedIntentActivitiesCount(contentUriIntent); - - if (contentUriActivitiesCount > 0) { - return new IntentAndResolvedActivitiesCount(contentUriIntent, contentUriActivitiesCount); - } - - File tempFile = TemporaryAttachmentStore.getFile(context, name); - Uri tempFileUri = Uri.fromFile(tempFile); - Intent fileUriIntent = createViewIntentForFileUri(mimeType, tempFileUri); - int fileUriActivitiesCount = getResolvedIntentActivitiesCount(fileUriIntent); - - if (fileUriActivitiesCount > 0) { - return new IntentAndResolvedActivitiesCount(fileUriIntent, fileUriActivitiesCount); - } - - return new IntentAndResolvedActivitiesCount(contentUriIntent, contentUriActivitiesCount); - } - - private Intent createViewIntentForAttachmentProviderUri(String mimeType) { - //FIXME - throw new RuntimeException("temporarily disabled"); -// Uri uri = AttachmentProvider.getAttachmentUriForViewing(account, part.getAttachmentId(), mimeType, name); -// -// Intent intent = new Intent(Intent.ACTION_VIEW); -// intent.setDataAndType(uri, mimeType); -// intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); -// addUiIntentFlags(intent); -// -// return intent; - } - - private Intent createViewIntentForFileUri(String mimeType, Uri uri) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(uri, mimeType); - addUiIntentFlags(intent); - - return intent; - } - - private void addUiIntentFlags(Intent intent) { - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - } - - private int getResolvedIntentActivitiesCount(Intent intent) { - PackageManager packageManager = context.getPackageManager(); - - List resolveInfos = - packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - - return resolveInfos.size(); - } - - private void displayAttachmentSavedMessage(final String filename) { - String message = context.getString(R.string.message_view_status_attachment_saved, filename); - displayMessageToUser(message); - } - - private void displayAttachmentNotSavedMessage() { - String message = context.getString(R.string.message_view_status_attachment_not_saved); - displayMessageToUser(message); - } - - private void displayMessageToUser(String message) { - Toast.makeText(context, message, Toast.LENGTH_LONG).show(); - } - - public void setCallback(AttachmentFileDownloadCallback callback) { + public void setCallback(AttachmentViewCallback callback) { this.callback = callback; } - - public interface AttachmentFileDownloadCallback { - /** - * This method is called to ask the user to pick a directory to save the attachment to. - *

- * After the user has selected a directory, the implementation of this interface has to call - * {@link #writeFile(File)} on the object supplied as argument in order for the attachment to be saved. - */ - public void pickDirectoryToSaveAttachmentTo(AttachmentView caller); - } - - - private static class IntentAndResolvedActivitiesCount { - private Intent intent; - private int activitiesCount; - - IntentAndResolvedActivitiesCount(Intent intent, int activitiesCount) { - this.intent = intent; - this.activitiesCount = activitiesCount; - } - - public Intent getIntent() { - return intent; - } - - public boolean hasResolvedActivities() { - return activitiesCount > 0; - } - - public String getMimeType() { - return intent.getType(); - } - - public boolean containsFileUri() { - return "file".equals(intent.getData().getScheme()); - } - } - private class LoadAndDisplayThumbnailAsyncTask extends AsyncTask { private final ImageView thumbnail; @@ -445,34 +158,4 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo } } } - - private class ViewAttachmentAsyncTask extends AsyncTask { - - @Override - protected void onPreExecute() { - viewButton.setEnabled(false); - } - - @Override - protected Intent doInBackground(Void... params) { - return getBestViewIntentAndSaveFileIfNecessary(); - } - - @Override - protected void onPostExecute(Intent intent) { - viewAttachment(intent); - viewButton.setEnabled(true); - } - - private void viewAttachment(Intent intent) { - try { - context.startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.e(K9.LOG_TAG, "Could not display attachment of type " + contentType, e); - - String message = context.getString(R.string.message_view_no_viewer, contentType); - displayMessageToUser(message); - } - } - } } diff --git a/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentViewCallback.java b/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentViewCallback.java new file mode 100644 index 000000000..3575bee69 --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/ui/messageview/AttachmentViewCallback.java @@ -0,0 +1,11 @@ +package com.fsck.k9.ui.messageview; + + +import com.fsck.k9.mailstore.AttachmentViewInfo; + + +interface AttachmentViewCallback { + void onViewAttachment(AttachmentViewInfo attachment); + void onSaveAttachment(AttachmentViewInfo attachment); + void onSaveAttachmentToUserProvidedDirectory(AttachmentViewInfo attachment); +} diff --git a/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java b/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java index 0722930d9..719708e1a 100644 --- a/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java +++ b/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java @@ -1,20 +1,19 @@ package com.fsck.k9.ui.messageview; -import java.io.File; + import java.util.Collections; import java.util.Locale; import android.app.Activity; +import android.app.DialogFragment; import android.app.Fragment; +import android.app.FragmentManager; import android.app.LoaderManager.LoaderCallbacks; import android.content.Context; import android.content.Intent; import android.content.Loader; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.app.DialogFragment; -import android.app.FragmentManager; import android.text.TextUtils; import android.util.Log; import android.view.ContextThemeWrapper; @@ -32,7 +31,6 @@ import com.fsck.k9.R; import com.fsck.k9.activity.ChooseFolder; import com.fsck.k9.activity.MessageReference; import com.fsck.k9.controller.MessagingController; -import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.crypto.PgpData; import com.fsck.k9.fragment.ConfirmationDialogFragment; import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener; @@ -40,21 +38,18 @@ import com.fsck.k9.fragment.ProgressDialogFragment; import com.fsck.k9.helper.FileBrowserHelper; import com.fsck.k9.helper.FileBrowserHelper.FileBrowserFailOverCallback; import com.fsck.k9.mail.Flag; -import com.fsck.k9.mail.Message; import com.fsck.k9.mail.MessagingException; -import com.fsck.k9.mail.Part; +import com.fsck.k9.mailstore.AttachmentViewInfo; import com.fsck.k9.mailstore.LocalMessage; import com.fsck.k9.mailstore.MessageViewInfo; import com.fsck.k9.ui.message.DecodeMessageLoader; import com.fsck.k9.ui.message.LocalMessageLoader; -import com.fsck.k9.ui.messageview.AttachmentView.AttachmentFileDownloadCallback; import com.fsck.k9.view.MessageHeader; - import org.openintents.openpgp.OpenPgpSignatureResult; -public class MessageViewFragment extends Fragment implements OnClickListener, - ConfirmationDialogFragmentListener { +public class MessageViewFragment extends Fragment implements OnClickListener, ConfirmationDialogFragmentListener, + AttachmentViewCallback { private static final String ARG_REFERENCE = "reference"; @@ -86,16 +81,8 @@ public class MessageViewFragment extends Fragment implements OnClickListener, private MessageReference mMessageReference; private LocalMessage mMessage; private MessagingController mController; - private Listener mListener = new Listener(); - private MessageViewHandler mHandler = new MessageViewHandler(); private LayoutInflater mLayoutInflater; - /** this variable is used to save the calling AttachmentView - * until the onActivityResult is called. - * => with this reference we can identity the caller - */ - private AttachmentView attachmentTmpStore; - /** * Used to temporarily store the destination folder for refile operations if a confirmation * dialog is shown. @@ -116,49 +103,9 @@ public class MessageViewFragment extends Fragment implements OnClickListener, private LoaderCallbacks localMessageLoaderCallback = new LocalMessageLoaderCallback(); private LoaderCallbacks decodeMessageLoaderCallback = new DecodeMessageLoaderCallback(); private MessageViewInfo messageViewInfo; + private AttachmentViewInfo currentAttachmentViewInfo; - class MessageViewHandler extends Handler { - - public void progress(final boolean progress) { - post(new Runnable() { - @Override - public void run() { - setProgress(progress); - } - }); - } - - /* A helper for a set of "show a toast" methods */ - private void showToast(final String message, final int toastLength) { - post(new Runnable() { - @Override - public void run() { - Toast.makeText(getActivity(), message, toastLength).show(); - } - }); - } - - public void networkError() { - // FIXME: This is a hack. Fix the Handler madness! - Context context = getActivity(); - if (context == null) { - return; - } - - showToast(context.getString(R.string.status_network_error), Toast.LENGTH_LONG); - } - - public void fetchingAttachment() { - Context context = getActivity(); - if (context == null) { - return; - } - - showToast(context.getString(R.string.message_view_fetching_attachment_toast), Toast.LENGTH_SHORT); - } - } - @Override public void onAttach(Activity activity) { super.onAttach(activity); @@ -195,33 +142,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener, mMessageView = (SingleMessageView) view.findViewById(R.id.message_view); - //set a callback for the attachment view. With this callback the attachmentview - //request the start of a filebrowser activity. - mMessageView.setAttachmentCallback(new AttachmentFileDownloadCallback() { - - @Override - public void pickDirectoryToSaveAttachmentTo(final AttachmentView caller) { - FileBrowserHelper.getInstance() - .showFileBrowserActivity(MessageViewFragment.this, - null, - ACTIVITY_CHOOSE_DIRECTORY, - callback); - attachmentTmpStore = caller; - } - - FileBrowserFailOverCallback callback = new FileBrowserFailOverCallback() { - - @Override - public void onPathEntered(String path) { - attachmentTmpStore.writeFile(new File(path)); - } - - @Override - public void onCancel() { - // canceled, do nothing - } - }; - }); + mMessageView.setAttachmentCallback(this); mMessageView.initialize(this, new OnClickListener() { @Override @@ -246,7 +167,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener, messageReference = (MessageReference) savedInstanceState.get(STATE_MESSAGE_REFERENCE); } else { Bundle args = getArguments(); - messageReference = (MessageReference) args.getParcelable(ARG_REFERENCE); + messageReference = args.getParcelable(ARG_REFERENCE); } displayMessage(messageReference, (mPgpData == null)); @@ -316,7 +237,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener, private void showMessage(MessageViewInfo messageContainer) { try { - mMessageView.setMessage(mAccount, messageContainer, mPgpData, mController, mListener); + mMessageView.setMessage(mAccount, messageContainer, mPgpData); mMessageView.setShowDownloadButton(mMessage); } catch (MessagingException e) { Log.e(K9.LOG_TAG, "Error while trying to display message", e); @@ -474,13 +395,13 @@ public class MessageViewFragment extends Fragment implements OnClickListener, switch (requestCode) { case ACTIVITY_CHOOSE_DIRECTORY: { - if (resultCode == Activity.RESULT_OK && data != null) { + if (data != null) { // obtain the filename Uri fileUri = data.getData(); if (fileUri != null) { String filePath = fileUri.getPath(); if (filePath != null) { - attachmentTmpStore.writeFile(new File(filePath)); + getAttachmentController(currentAttachmentViewInfo).saveAttachmentTo(filePath); } } } @@ -535,7 +456,8 @@ public class MessageViewFragment extends Fragment implements OnClickListener, return; } mMessageView.downloadRemainderButton().setEnabled(false); - mController.loadMessageForViewRemote(mAccount, mMessageReference.folderName, mMessageReference.uid, mListener); + //FIXME +// mController.loadMessageForViewRemote(mAccount, mMessageReference.folderName, mMessageReference.uid, mListener); } @Override @@ -576,89 +498,6 @@ public class MessageViewFragment extends Fragment implements OnClickListener, destFolderName, null); } - class Listener extends MessagingListener { - @Override - public void loadMessageForViewHeadersAvailable(final Account account, String folder, String uid, - final Message message) { - throw new IllegalStateException(); - } - - @Override - public void loadMessageForViewBodyAvailable(final Account account, String folder, - String uid, final Message message) { - throw new IllegalStateException(); - } - - @Override - public void loadMessageForViewFailed(Account account, String folder, String uid, final Throwable t) { - throw new IllegalStateException(); - } - - @Override - public void loadMessageForViewFinished(Account account, String folder, String uid, final Message message) { - throw new IllegalStateException(); - } - - @Override - public void loadMessageForViewStarted(Account account, String folder, String uid) { - throw new IllegalStateException(); - } - - @Override - public void loadAttachmentStarted(Account account, Message message, Part part, Object tag, final boolean requiresDownload) { - if (mMessage != message) { - return; - } - mHandler.post(new Runnable() { - @Override - public void run() { - mMessageView.setAttachmentsEnabled(false); - showDialog(R.id.dialog_attachment_progress); - if (requiresDownload) { - mHandler.fetchingAttachment(); - } - } - }); - } - - @Override - public void loadAttachmentFinished(Account account, Message message, Part part, final Object tag) { - if (mMessage != message) { - return; - } - mHandler.post(new Runnable() { - @Override - public void run() { - mMessageView.setAttachmentsEnabled(true); - removeDialog(R.id.dialog_attachment_progress); - Object[] params = (Object[]) tag; - boolean download = (Boolean) params[0]; - AttachmentView attachment = (AttachmentView) params[1]; - if (download) { - attachment.writeFile(); - } else { - attachment.showFile(); - } - } - }); - } - - @Override - public void loadAttachmentFailed(Account account, Message message, Part part, Object tag, String reason) { - if (mMessage != message) { - return; - } - mHandler.post(new Runnable() { - @Override - public void run() { - mMessageView.setAttachmentsEnabled(true); - removeDialog(R.id.dialog_attachment_progress); - mHandler.networkError(); - } - }); - } - } - /** * Used by MessageOpenPgpView */ @@ -668,7 +507,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener, PgpData data = new PgpData(); data.setDecryptedData(decryptedData); data.setSignatureResult(signatureResult); - mMessageView.setMessage(mAccount, messageViewInfo, data, mController, mListener); + mMessageView.setMessage(mAccount, messageViewInfo, data); } catch (MessagingException e) { Log.e(K9.LOG_TAG, "displayMessageBody failed", e); } @@ -860,4 +699,41 @@ public class MessageViewFragment extends Fragment implements OnClickListener, // Do nothing } } + + @Override + public void onViewAttachment(AttachmentViewInfo attachment) { + //TODO: check if we have to download the attachment first + + getAttachmentController(attachment).viewAttachment(); + } + + @Override + public void onSaveAttachment(AttachmentViewInfo attachment) { + //TODO: check if we have to download the attachment first + + getAttachmentController(attachment).saveAttachment(); + } + + @Override + public void onSaveAttachmentToUserProvidedDirectory(final AttachmentViewInfo attachment) { + //TODO: check if we have to download the attachment first + + currentAttachmentViewInfo = attachment; + FileBrowserHelper.getInstance().showFileBrowserActivity(MessageViewFragment.this, null, + ACTIVITY_CHOOSE_DIRECTORY, new FileBrowserFailOverCallback() { + @Override + public void onPathEntered(String path) { + getAttachmentController(attachment).saveAttachmentTo(path); + } + + @Override + public void onCancel() { + // Do nothing + } + }); + } + + private AttachmentController getAttachmentController(AttachmentViewInfo attachment) { + return new AttachmentController(mMessageView, attachment); + } } diff --git a/k9mail/src/main/java/com/fsck/k9/ui/messageview/SingleMessageView.java b/k9mail/src/main/java/com/fsck/k9/ui/messageview/SingleMessageView.java index 586da1a9a..33086ea04 100644 --- a/k9mail/src/main/java/com/fsck/k9/ui/messageview/SingleMessageView.java +++ b/k9mail/src/main/java/com/fsck/k9/ui/messageview/SingleMessageView.java @@ -5,6 +5,8 @@ import java.io.FileOutputStream; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; +import java.util.HashMap; +import java.util.Map; import android.app.Activity; import android.app.Fragment; @@ -39,8 +41,6 @@ import android.widget.Toast; import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.R; -import com.fsck.k9.controller.MessagingController; -import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.crypto.PgpData; import com.fsck.k9.helper.ClipboardManager; import com.fsck.k9.helper.Contacts; @@ -52,12 +52,11 @@ import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.Message; import com.fsck.k9.mail.MessagingException; -import com.fsck.k9.mail.Part; import com.fsck.k9.mail.internet.MimeUtility; +import com.fsck.k9.mailstore.AttachmentViewInfo; import com.fsck.k9.mailstore.MessageViewInfo; import com.fsck.k9.provider.AttachmentProvider.AttachmentProviderColumns; -import com.fsck.k9.ui.messageview.AttachmentView.AttachmentFileDownloadCallback; import com.fsck.k9.view.MessageHeader; import com.fsck.k9.view.MessageHeader.OnLayoutChangedListener; import com.fsck.k9.view.MessageWebView; @@ -103,11 +102,12 @@ public class SingleMessageView extends LinearLayout implements OnClickListener, private Button mDownloadRemainder; private LayoutInflater mInflater; private Contacts mContacts; - private AttachmentFileDownloadCallback attachmentCallback; + private AttachmentViewCallback attachmentCallback; private View mAttachmentsContainer; private SavedState mSavedState; private ClipboardManager mClipboardManager; private String mText; + private Map attachments = new HashMap(); public void initialize(Fragment fragment, OnClickListener flagListener) { @@ -497,8 +497,8 @@ public class SingleMessageView extends LinearLayout implements OnClickListener, return mHeaderContainer.additionalHeadersVisible(); } - public void setMessage(Account account, MessageViewInfo messageViewInfo, PgpData pgpData, - MessagingController controller, MessagingListener listener) throws MessagingException { + public void setMessage(Account account, MessageViewInfo messageViewInfo, PgpData pgpData) + throws MessagingException { resetView(); String text = null; @@ -517,9 +517,8 @@ public class SingleMessageView extends LinearLayout implements OnClickListener, mText = text; mHasAttachments = !messageViewInfo.attachments.isEmpty(); - if (mHasAttachments) { - renderAttachments(messageViewInfo, account, controller, listener); + renderAttachments(messageViewInfo); } mHiddenAttachments.setVisibility(View.GONE); @@ -593,30 +592,15 @@ public class SingleMessageView extends LinearLayout implements OnClickListener, mMessageContentView.setVisibility(show ? View.VISIBLE : View.GONE); } - public void setAttachmentsEnabled(boolean enabled) { - for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) { - AttachmentView attachment = (AttachmentView) mAttachments.getChildAt(i); - attachment.setButtonsEnabled(enabled); - } - } - - public void removeAllAttachments() { - for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) { - mAttachments.removeView(mAttachments.getChildAt(i)); - } - } - - public void renderAttachments(MessageViewInfo messageContainer, Account account, MessagingController controller, - MessagingListener listener) throws MessagingException { - - for (Part attachment : messageContainer.attachments) { + public void renderAttachments(MessageViewInfo messageContainer) throws MessagingException { + for (AttachmentViewInfo attachment : messageContainer.attachments) { AttachmentView view = (AttachmentView) mInflater.inflate(R.layout.message_view_attachment, null); view.setCallback(attachmentCallback); + view.setAttachment(attachment); - boolean isFirstClassAttachment = view.populateFromPart(attachment, messageContainer.message, account, - controller, listener); + attachments.put(attachment, view); - if (isFirstClassAttachment) { + if (attachment.firstClassAttachment) { addAttachment(view); } else { addHiddenAttachment(view); @@ -667,12 +651,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener, mHeaderContainer.setVisibility(View.GONE); } - public AttachmentView.AttachmentFileDownloadCallback getAttachmentCallback() { - return attachmentCallback; - } - - public void setAttachmentCallback( - AttachmentView.AttachmentFileDownloadCallback attachmentCallback) { + public void setAttachmentCallback(AttachmentViewCallback attachmentCallback) { this.attachmentCallback = attachmentCallback; } @@ -711,6 +690,18 @@ public class SingleMessageView extends LinearLayout implements OnClickListener, } } + public void enableAttachmentViewButton(AttachmentViewInfo attachment) { + getAttachmentView(attachment).enableViewButton(); + } + + public void disableAttachmentViewButton(AttachmentViewInfo attachment) { + getAttachmentView(attachment).disableViewButton(); + } + + private AttachmentView getAttachmentView(AttachmentViewInfo attachment) { + return attachments.get(attachment); + } + static class SavedState extends BaseSavedState { boolean attachmentViewVisible; boolean hiddenAttachmentsVisible;