Move functionality from AttachmentView to AttachmentController

This commit is contained in:
cketti 2015-01-15 11:19:32 +01:00
parent 087238f507
commit 8fce9e3654
8 changed files with 458 additions and 587 deletions

View File

@ -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;
}
}

View File

@ -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<Part>());
}
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<AttachmentViewInfo> attachments = extractAttachmentInfos(viewable.attachments);
return new MessageViewInfo(viewable.html, attachments, message);
}
private static List<AttachmentViewInfo> extractAttachmentInfos(List<Part> attachmentParts)
throws MessagingException {
List<AttachmentViewInfo> attachments = new ArrayList<AttachmentViewInfo>();
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);
}
}

View File

@ -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<Part> attachments;
public final List<AttachmentViewInfo> attachments;
public final Message message;
public MessageViewInfo(String text, List<Part> attachments, Message message) {
public MessageViewInfo(String text, List<AttachmentViewInfo> attachments, Message message) {
this.text = text;
this.attachments = Collections.unmodifiableList(attachments);
this.message = message;

View File

@ -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<ResolveInfo> 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<Void, Void, Intent> {
@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);
}
}
}
}

View File

@ -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.
* <p>
* 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.<br>
* Inline attachments with content ID and unnamed attachments fall into the second category.
* </p>
*
* @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<ResolveInfo> 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.
* <p/>
* 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<Void, Void, Bitmap> {
private final ImageView thumbnail;
@ -445,34 +158,4 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
}
}
}
private class ViewAttachmentAsyncTask extends AsyncTask<Void, Void, Intent> {
@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);
}
}
}
}

View File

@ -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);
}

View File

@ -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<LocalMessage> localMessageLoaderCallback = new LocalMessageLoaderCallback();
private LoaderCallbacks<MessageViewInfo> 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);
}
}

View File

@ -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<AttachmentViewInfo, AttachmentView> attachments = new HashMap<AttachmentViewInfo, AttachmentView>();
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;