Use a Loader to extract text of a message in a background thread

This commit is contained in:
cketti 2015-01-14 09:56:57 +01:00
parent 78ed2a23b1
commit 2e05127c97
6 changed files with 176 additions and 79 deletions

View File

@ -467,4 +467,10 @@ public class LocalMessageExtractor {
} }
return new ViewableContainer(text, html, attachments); 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);
}
} }

View File

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

View File

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

View File

@ -44,6 +44,8 @@ import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part; import com.fsck.k9.mail.Part;
import com.fsck.k9.mailstore.LocalMessage; 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.message.LocalMessageLoader;
import com.fsck.k9.view.AttachmentView; import com.fsck.k9.view.AttachmentView;
import com.fsck.k9.view.AttachmentView.AttachmentFileDownloadCallback; 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 ACTIVITY_CHOOSE_DIRECTORY = 3;
private static final int LOCAL_MESSAGE_LOADER_ID = 1; private static final int LOCAL_MESSAGE_LOADER_ID = 1;
private static final int DECODE_MESSAGE_LOADER_ID = 2;
public static MessageViewFragment newInstance(MessageReference reference) { public static MessageViewFragment newInstance(MessageReference reference) {
@ -112,6 +115,8 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
private Context mContext; private Context mContext;
private LoaderCallbacks<LocalMessage> localMessageLoaderCallback = new LocalMessageLoaderCallback(); private LoaderCallbacks<LocalMessage> localMessageLoaderCallback = new LocalMessageLoaderCallback();
private LoaderCallbacks<MessageViewInfo> decodeMessageLoaderCallback = new DecodeMessageLoaderCallback();
private MessageViewInfo messageViewInfo;
class MessageViewHandler extends Handler { class MessageViewHandler extends Handler {
@ -301,11 +306,19 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
} }
private void startExtractingTextAndAttachments(LocalMessage message) { 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 //TODO: handle decryption and signature verification
this.messageViewInfo = messageContainer;
showMessage(messageContainer);
}
private void showMessage(MessageViewInfo messageContainer) {
try { try {
mMessageView.setMessage(mAccount, message, mPgpData, mController, mListener); mMessageView.setMessage(mAccount, messageContainer, mPgpData, mController, mListener);
mMessageView.setShowDownloadButton(message); mMessageView.setShowDownloadButton(mMessage);
} catch (MessagingException e) { } catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Error while trying to display message", 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(); PgpData data = new PgpData();
data.setDecryptedData(decryptedData); data.setDecryptedData(decryptedData);
data.setSignatureResult(signatureResult); data.setSignatureResult(signatureResult);
mMessageView.setMessage(mAccount, (LocalMessage) mMessage, data, mController, mListener); mMessageView.setMessage(mAccount, messageViewInfo, data, mController, mListener);
} catch (MessagingException e) { } catch (MessagingException e) {
Log.e(K9.LOG_TAG, "displayMessageBody failed", e); Log.e(K9.LOG_TAG, "displayMessageBody failed", e);
} }
@ -809,11 +822,13 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
class LocalMessageLoaderCallback implements LoaderCallbacks<LocalMessage> { class LocalMessageLoaderCallback implements LoaderCallbacks<LocalMessage> {
@Override @Override
public Loader<LocalMessage> onCreateLoader(int id, Bundle args) { public Loader<LocalMessage> onCreateLoader(int id, Bundle args) {
setProgress(true);
return new LocalMessageLoader(mContext, mController, mAccount, mMessageReference); return new LocalMessageLoader(mContext, mController, mAccount, mMessageReference);
} }
@Override @Override
public void onLoadFinished(Loader<LocalMessage> loader, LocalMessage message) { public void onLoadFinished(Loader<LocalMessage> loader, LocalMessage message) {
setProgress(false);
mMessage = message; mMessage = message;
if (message == null) { if (message == null) {
onLoadMessageFromDatabaseFailed(); onLoadMessageFromDatabaseFailed();
@ -827,4 +842,23 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
// Do nothing // Do nothing
} }
} }
class DecodeMessageLoaderCallback implements LoaderCallbacks<MessageViewInfo> {
@Override
public Loader<MessageViewInfo> onCreateLoader(int id, Bundle args) {
setProgress(true);
return new DecodeMessageLoader(mContext, mMessage);
}
@Override
public void onLoadFinished(Loader<MessageViewInfo> loader, MessageViewInfo messageContainer) {
setProgress(false);
onDecodeMessageFinished(messageContainer);
}
@Override
public void onLoaderReset(Loader<MessageViewInfo> loader) {
// Do nothing
}
}
} }

View File

@ -52,13 +52,9 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part; import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mailstore.LocalAttachmentBodyPart; import com.fsck.k9.mailstore.MessageViewInfo;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.LocalMessageExtractor;
import com.fsck.k9.mailstore.ViewableContainer;
import com.fsck.k9.provider.AttachmentProvider.AttachmentProviderColumns; import com.fsck.k9.provider.AttachmentProvider.AttachmentProviderColumns;
import com.fsck.k9.view.AttachmentView; import com.fsck.k9.view.AttachmentView;
@ -502,7 +498,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
return mHeaderContainer.additionalHeadersVisible(); 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 { MessagingController controller, MessagingListener listener) throws MessagingException {
resetView(); resetView();
@ -515,18 +511,16 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
} }
if (text == null) { if (text == null) {
//FIXME: Run the text extraction in a background thread because it might involve disk I/O text = messageViewInfo.text;
ViewableContainer viewables = LocalMessageExtractor.extractTextAndAttachments(getContext(), message);
text = viewables.html;
} }
// Save the text so we can reset the WebView when the user clicks the "Show pictures" button // Save the text so we can reset the WebView when the user clicks the "Show pictures" button
mText = text; mText = text;
mHasAttachments = message.hasAttachments(); mHasAttachments = !messageViewInfo.attachments.isEmpty();
if (mHasAttachments) { if (mHasAttachments) {
renderAttachments(message, 0, message, account, controller, listener); renderAttachments(messageViewInfo, account, controller, listener);
} }
mHiddenAttachments.setVisibility(View.GONE); 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 // button wasn't already pressed, see if the user's preferences has us
// showing them anyway. // showing them anyway.
if (Utility.hasExternalImages(text) && !showPictures()) { if (Utility.hasExternalImages(text) && !showPictures()) {
Address[] from = message.getFrom(); Address[] from = messageViewInfo.message.getFrom();
if ((account.getShowPictures() == Account.ShowPictures.ALWAYS) || if ((account.getShowPictures() == Account.ShowPictures.ALWAYS) ||
((account.getShowPictures() == Account.ShowPictures.ONLY_FROM_CONTACTS) && ((account.getShowPictures() == Account.ShowPictures.ONLY_FROM_CONTACTS) &&
// Make sure we have at least one from address // Make sure we have at least one from address
@ -574,7 +568,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
if (text != null) { if (text != null) {
loadBodyFromText(text); loadBodyFromText(text);
mOpenPgpView.updateLayout(account, pgpData.getDecryptedData(), mOpenPgpView.updateLayout(account, pgpData.getDecryptedData(),
pgpData.getSignatureResult(), message); pgpData.getSignatureResult(), messageViewInfo.message);
} else { } else {
showStatusMessage(getContext().getString(R.string.webview_empty_message)); 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, public void renderAttachments(MessageViewInfo messageContainer, Account account, MessagingController controller,
MessagingController controller, MessagingListener listener) throws MessagingException { MessagingListener listener) throws MessagingException {
if (part.getBody() instanceof Multipart) { for (Part attachment : messageContainer.attachments) {
Multipart mp = (Multipart) part.getBody(); AttachmentView view = (AttachmentView) mInflater.inflate(R.layout.message_view_attachment, null);
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);
view.setCallback(attachmentCallback); view.setCallback(attachmentCallback);
try { boolean isFirstClassAttachment = view.populateFromPart(attachment, messageContainer.message, account,
if (view.populateFromPart(part, message, account, controller, listener)) { controller, listener);
addAttachment(view);
} else { if (isFirstClassAttachment) {
addHiddenAttachment(view); addAttachment(view);
} } else {
} catch (Exception e) { addHiddenAttachment(view);
Log.e(K9.LOG_TAG, "Error adding attachment view", e);
} }
} }
} }

View File

@ -2,10 +2,7 @@ package com.fsck.k9.view;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List; import java.util.List;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
@ -14,7 +11,6 @@ import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Environment; 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.Part;
import com.fsck.k9.mail.internet.MimeHeader; import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeUtility; 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 { public class AttachmentView extends FrameLayout implements OnClickListener, OnLongClickListener {
private Context context; private Context context;
private Message message; private Message message;
private LocalAttachmentBodyPart part; private Part part;
private Account account; private Account account;
private MessagingController controller; private MessagingController controller;
private MessagingListener listener; 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. * @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 { MessagingController controller, MessagingListener listener) throws MessagingException {
part = (LocalAttachmentBodyPart) inputPart; this.part = part;
this.message = message; this.message = message;
this.account = account; this.account = account;
this.controller = controller; this.controller = controller;
@ -247,19 +240,21 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
} }
private void writeAttachmentToStorage(File file) throws IOException { private void writeAttachmentToStorage(File file) throws IOException {
Uri uri = AttachmentProvider.getAttachmentUri(account, part.getAttachmentId()); //FIXME
InputStream in = context.getContentResolver().openInputStream(uri); throw new RuntimeException("temporarily disabled");
try { // Uri uri = AttachmentProvider.getAttachmentUri(account, part.getAttachmentId());
OutputStream out = new FileOutputStream(file); // InputStream in = context.getContentResolver().openInputStream(uri);
try { // try {
IOUtils.copy(in, out); // OutputStream out = new FileOutputStream(file);
out.flush(); // try {
} finally { // IOUtils.copy(in, out);
out.close(); // out.flush();
} // } finally {
} finally { // out.close();
in.close(); // }
} // } finally {
// in.close();
// }
} }
public void showFile() { public void showFile() {
@ -323,14 +318,16 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
} }
private Intent createViewIntentForAttachmentProviderUri(String mimeType) { private Intent createViewIntentForAttachmentProviderUri(String mimeType) {
Uri uri = AttachmentProvider.getAttachmentUriForViewing(account, part.getAttachmentId(), mimeType, name); //FIXME
throw new RuntimeException("temporarily disabled");
Intent intent = new Intent(Intent.ACTION_VIEW); // Uri uri = AttachmentProvider.getAttachmentUriForViewing(account, part.getAttachmentId(), mimeType, name);
intent.setDataAndType(uri, mimeType); //
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Intent intent = new Intent(Intent.ACTION_VIEW);
addUiIntentFlags(intent); // intent.setDataAndType(uri, mimeType);
// intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
return intent; // addUiIntentFlags(intent);
//
// return intent;
} }
private Intent createViewIntentForFileUri(String mimeType, Uri uri) { private Intent createViewIntentForFileUri(String mimeType, Uri uri) {
@ -422,20 +419,22 @@ public class AttachmentView extends FrameLayout implements OnClickListener, OnLo
} }
private Bitmap getPreviewIcon() { private Bitmap getPreviewIcon() {
Bitmap icon = null; //FIXME - temporarily disabled
try { return null;
InputStream input = context.getContentResolver().openInputStream( // Bitmap icon = null;
AttachmentProvider.getAttachmentThumbnailUri(account, // try {
part.getAttachmentId(), // InputStream input = context.getContentResolver().openInputStream(
62, // AttachmentProvider.getAttachmentThumbnailUri(account,
62)); // part.getAttachmentId(),
icon = BitmapFactory.decodeStream(input); // 62,
input.close(); // 62));
} catch (Exception e) { // icon = BitmapFactory.decodeStream(input);
// We don't care what happened, we just return null for the preview icon. // input.close();
} // } catch (Exception e) {
// // We don't care what happened, we just return null for the preview icon.
return icon; // }
//
// return icon;
} }
protected void onPostExecute(Bitmap previewIcon) { protected void onPostExecute(Bitmap previewIcon) {