From 2e05127c97e8764fc54cf1dfa627c145f9b1b44f Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 14 Jan 2015 09:56:57 +0100 Subject: [PATCH] Use a Loader to extract text of a message in a background thread --- .../k9/mailstore/LocalMessageExtractor.java | 6 ++ .../fsck/k9/mailstore/MessageViewInfo.java | 21 +++++ .../k9/ui/message/DecodeMessageLoader.java | 49 ++++++++++ .../ui/messageview/MessageViewFragment.java | 42 ++++++++- .../k9/ui/messageview/SingleMessageView.java | 48 ++++------ .../java/com/fsck/k9/view/AttachmentView.java | 89 +++++++++---------- 6 files changed, 176 insertions(+), 79 deletions(-) create mode 100644 k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfo.java create mode 100644 k9mail/src/main/java/com/fsck/k9/ui/message/DecodeMessageLoader.java 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 229a166fe..6a4b61170 100644 --- a/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java @@ -467,4 +467,10 @@ public class LocalMessageExtractor { } return new ViewableContainer(text, html, attachments); } + + 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); + } } diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfo.java b/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfo.java new file mode 100644 index 000000000..6e359bbbc --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/MessageViewInfo.java @@ -0,0 +1,21 @@ +package com.fsck.k9.mailstore; + + +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 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/message/DecodeMessageLoader.java b/k9mail/src/main/java/com/fsck/k9/ui/message/DecodeMessageLoader.java new file mode 100644 index 000000000..27a0a5e00 --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/ui/message/DecodeMessageLoader.java @@ -0,0 +1,49 @@ +package com.fsck.k9.ui.message; + + +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.util.Log; + +import com.fsck.k9.K9; +import com.fsck.k9.mail.Message; +import com.fsck.k9.mailstore.LocalMessageExtractor; +import com.fsck.k9.mailstore.MessageViewInfo; + + +public class DecodeMessageLoader extends AsyncTaskLoader { + private final Message message; + private MessageViewInfo messageViewInfo; + + public DecodeMessageLoader(Context context, Message message) { + super(context); + this.message = message; + } + + @Override + protected void onStartLoading() { + if (messageViewInfo != null) { + super.deliverResult(messageViewInfo); + } + + if (takeContentChanged() || messageViewInfo == null) { + forceLoad(); + } + } + + @Override + public void deliverResult(MessageViewInfo messageViewInfo) { + this.messageViewInfo = messageViewInfo; + super.deliverResult(messageViewInfo); + } + + @Override + public MessageViewInfo loadInBackground() { + try { + return LocalMessageExtractor.decodeMessageForView(getContext(), message); + } catch (Exception e) { + Log.e(K9.LOG_TAG, "Error while decoding message", e); + return null; + } + } +} 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 47740d65f..4ced13aaf 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 @@ -44,6 +44,8 @@ import com.fsck.k9.mail.Message; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.Part; 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.view.AttachmentView; import com.fsck.k9.view.AttachmentView.AttachmentFileDownloadCallback; @@ -65,6 +67,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener, private static final int ACTIVITY_CHOOSE_DIRECTORY = 3; private static final int LOCAL_MESSAGE_LOADER_ID = 1; + private static final int DECODE_MESSAGE_LOADER_ID = 2; public static MessageViewFragment newInstance(MessageReference reference) { @@ -112,6 +115,8 @@ public class MessageViewFragment extends Fragment implements OnClickListener, private Context mContext; private LoaderCallbacks localMessageLoaderCallback = new LocalMessageLoaderCallback(); + private LoaderCallbacks decodeMessageLoaderCallback = new DecodeMessageLoaderCallback(); + private MessageViewInfo messageViewInfo; class MessageViewHandler extends Handler { @@ -301,11 +306,19 @@ public class MessageViewFragment extends Fragment implements OnClickListener, } private void startExtractingTextAndAttachments(LocalMessage message) { - //TODO: extract in background thread + getLoaderManager().initLoader(DECODE_MESSAGE_LOADER_ID, null, decodeMessageLoaderCallback); + } + + private void onDecodeMessageFinished(MessageViewInfo messageContainer) { //TODO: handle decryption and signature verification + this.messageViewInfo = messageContainer; + showMessage(messageContainer); + } + + private void showMessage(MessageViewInfo messageContainer) { try { - mMessageView.setMessage(mAccount, message, mPgpData, mController, mListener); - mMessageView.setShowDownloadButton(message); + mMessageView.setMessage(mAccount, messageContainer, mPgpData, mController, mListener); + mMessageView.setShowDownloadButton(mMessage); } catch (MessagingException e) { Log.e(K9.LOG_TAG, "Error while trying to display message", e); } @@ -656,7 +669,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener, PgpData data = new PgpData(); data.setDecryptedData(decryptedData); data.setSignatureResult(signatureResult); - mMessageView.setMessage(mAccount, (LocalMessage) mMessage, data, mController, mListener); + mMessageView.setMessage(mAccount, messageViewInfo, data, mController, mListener); } catch (MessagingException e) { Log.e(K9.LOG_TAG, "displayMessageBody failed", e); } @@ -809,11 +822,13 @@ public class MessageViewFragment extends Fragment implements OnClickListener, class LocalMessageLoaderCallback implements LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { + setProgress(true); return new LocalMessageLoader(mContext, mController, mAccount, mMessageReference); } @Override public void onLoadFinished(Loader loader, LocalMessage message) { + setProgress(false); mMessage = message; if (message == null) { onLoadMessageFromDatabaseFailed(); @@ -827,4 +842,23 @@ public class MessageViewFragment extends Fragment implements OnClickListener, // Do nothing } } + + class DecodeMessageLoaderCallback implements LoaderCallbacks { + @Override + public Loader onCreateLoader(int id, Bundle args) { + setProgress(true); + return new DecodeMessageLoader(mContext, mMessage); + } + + @Override + public void onLoadFinished(Loader loader, MessageViewInfo messageContainer) { + setProgress(false); + onDecodeMessageFinished(messageContainer); + } + + @Override + public void onLoaderReset(Loader loader) { + // Do nothing + } + } } 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 fbe7d01f4..5c8afbb12 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 @@ -52,13 +52,9 @@ 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.Multipart; import com.fsck.k9.mail.Part; import com.fsck.k9.mail.internet.MimeUtility; -import com.fsck.k9.mailstore.LocalAttachmentBodyPart; -import com.fsck.k9.mailstore.LocalMessage; -import com.fsck.k9.mailstore.LocalMessageExtractor; -import com.fsck.k9.mailstore.ViewableContainer; +import com.fsck.k9.mailstore.MessageViewInfo; import com.fsck.k9.provider.AttachmentProvider.AttachmentProviderColumns; import com.fsck.k9.view.AttachmentView; @@ -502,7 +498,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener, return mHeaderContainer.additionalHeadersVisible(); } - public void setMessage(Account account, LocalMessage message, PgpData pgpData, + public void setMessage(Account account, MessageViewInfo messageViewInfo, PgpData pgpData, MessagingController controller, MessagingListener listener) throws MessagingException { resetView(); @@ -515,18 +511,16 @@ public class SingleMessageView extends LinearLayout implements OnClickListener, } if (text == null) { - //FIXME: Run the text extraction in a background thread because it might involve disk I/O - ViewableContainer viewables = LocalMessageExtractor.extractTextAndAttachments(getContext(), message); - text = viewables.html; + text = messageViewInfo.text; } // Save the text so we can reset the WebView when the user clicks the "Show pictures" button mText = text; - mHasAttachments = message.hasAttachments(); + mHasAttachments = !messageViewInfo.attachments.isEmpty(); if (mHasAttachments) { - renderAttachments(message, 0, message, account, controller, listener); + renderAttachments(messageViewInfo, account, controller, listener); } mHiddenAttachments.setVisibility(View.GONE); @@ -558,7 +552,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener, // button wasn't already pressed, see if the user's preferences has us // showing them anyway. if (Utility.hasExternalImages(text) && !showPictures()) { - Address[] from = message.getFrom(); + Address[] from = messageViewInfo.message.getFrom(); if ((account.getShowPictures() == Account.ShowPictures.ALWAYS) || ((account.getShowPictures() == Account.ShowPictures.ONLY_FROM_CONTACTS) && // Make sure we have at least one from address @@ -574,7 +568,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener, if (text != null) { loadBodyFromText(text); mOpenPgpView.updateLayout(account, pgpData.getDecryptedData(), - pgpData.getSignatureResult(), message); + pgpData.getSignatureResult(), messageViewInfo.message); } else { showStatusMessage(getContext().getString(R.string.webview_empty_message)); } @@ -613,26 +607,20 @@ public class SingleMessageView extends LinearLayout implements OnClickListener, } } - public void renderAttachments(Part part, int depth, Message message, Account account, - MessagingController controller, MessagingListener listener) throws MessagingException { + public void renderAttachments(MessageViewInfo messageContainer, Account account, MessagingController controller, + MessagingListener listener) throws MessagingException { - if (part.getBody() instanceof Multipart) { - Multipart mp = (Multipart) part.getBody(); - for (int i = 0; i < mp.getCount(); i++) { - renderAttachments(mp.getBodyPart(i), depth + 1, message, account, controller, listener); - } - } else if (part instanceof LocalAttachmentBodyPart) { - AttachmentView view = (AttachmentView)mInflater.inflate(R.layout.message_view_attachment, null); + for (Part attachment : messageContainer.attachments) { + AttachmentView view = (AttachmentView) mInflater.inflate(R.layout.message_view_attachment, null); view.setCallback(attachmentCallback); - 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); + boolean isFirstClassAttachment = view.populateFromPart(attachment, messageContainer.message, account, + controller, listener); + + if (isFirstClassAttachment) { + addAttachment(view); + } else { + addHiddenAttachment(view); } } } diff --git a/k9mail/src/main/java/com/fsck/k9/view/AttachmentView.java b/k9mail/src/main/java/com/fsck/k9/view/AttachmentView.java index 7552c23b5..de3d27bf7 100644 --- a/k9mail/src/main/java/com/fsck/k9/view/AttachmentView.java +++ b/k9mail/src/main/java/com/fsck/k9/view/AttachmentView.java @@ -2,10 +2,7 @@ package com.fsck.k9.view; 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; @@ -14,7 +11,6 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; import android.os.Environment; @@ -43,15 +39,12 @@ 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.LocalAttachmentBodyPart; -import com.fsck.k9.provider.AttachmentProvider; -import org.apache.commons.io.IOUtils; public class AttachmentView extends FrameLayout implements OnClickListener, OnLongClickListener { private Context context; private Message message; - private LocalAttachmentBodyPart part; + private Part part; private Account account; private MessagingController controller; private MessagingListener listener; @@ -96,10 +89,10 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo * * @return {@code true} for a regular attachment. {@code false} for attachments that should be initially hidden. */ - public boolean populateFromPart(Part inputPart, Message message, Account account, + public boolean populateFromPart(Part part, Message message, Account account, MessagingController controller, MessagingListener listener) throws MessagingException { - part = (LocalAttachmentBodyPart) inputPart; + this.part = part; this.message = message; this.account = account; this.controller = controller; @@ -247,19 +240,21 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo } private void writeAttachmentToStorage(File file) throws IOException { - 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(); - } + //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() { @@ -323,14 +318,16 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo } private Intent createViewIntentForAttachmentProviderUri(String mimeType) { - 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; + //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) { @@ -422,20 +419,22 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo } private Bitmap getPreviewIcon() { - Bitmap icon = null; - try { - InputStream input = context.getContentResolver().openInputStream( - AttachmentProvider.getAttachmentThumbnailUri(account, - part.getAttachmentId(), - 62, - 62)); - icon = BitmapFactory.decodeStream(input); - input.close(); - } catch (Exception e) { - // We don't care what happened, we just return null for the preview icon. - } - - return icon; + //FIXME - temporarily disabled + return null; +// Bitmap icon = null; +// try { +// InputStream input = context.getContentResolver().openInputStream( +// AttachmentProvider.getAttachmentThumbnailUri(account, +// part.getAttachmentId(), +// 62, +// 62)); +// icon = BitmapFactory.decodeStream(input); +// input.close(); +// } catch (Exception e) { +// // We don't care what happened, we just return null for the preview icon. +// } +// +// return icon; } protected void onPostExecute(Bitmap previewIcon) {