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);
}
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.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<LocalMessage> localMessageLoaderCallback = new LocalMessageLoaderCallback();
private LoaderCallbacks<MessageViewInfo> 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<LocalMessage> {
@Override
public Loader<LocalMessage> onCreateLoader(int id, Bundle args) {
setProgress(true);
return new LocalMessageLoader(mContext, mController, mAccount, mMessageReference);
}
@Override
public void onLoadFinished(Loader<LocalMessage> 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<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.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);
}
}
}

View File

@ -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) {