Use a Loader to load the message to view from the database

This commit is contained in:
cketti 2015-01-13 04:17:25 +01:00
parent 787c014265
commit 78ed2a23b1
6 changed files with 179 additions and 151 deletions

View File

@ -3113,27 +3113,34 @@ public class MessagingController implements Runnable {
}); });
} }
/** public LocalMessage loadMessage(Account account, String folderName, String uid) throws MessagingException {
* Mark the provided message as read if not disabled by the account setting. LocalStore localStore = account.getLocalStore();
* LocalFolder localFolder = localStore.getFolder(folderName);
* @param account localFolder.open(Folder.OPEN_MODE_RW);
* The account the message belongs to.
* @param message LocalMessage message = localFolder.getMessage(uid);
* The message to mark as read. This {@link Message} instance will be modify by calling if (message == null || message.getId() == 0) {
* {@link Message#setFlag(Flag, boolean)} on it. throw new IllegalArgumentException("Message not found: folder=" + folderName + ", uid=" + uid);
* }
* @throws MessagingException
* FetchProfile fp = new FetchProfile();
* @see Account#isMarkMessageAsReadOnView() fp.add(FetchProfile.Item.BODY);
*/ localFolder.fetch(Collections.singletonList(message), fp, null);
private void markMessageAsReadOnView(Account account, Message message) localFolder.close();
markMessageAsReadOnView(account, message);
return message;
}
private void markMessageAsReadOnView(Account account, LocalMessage message)
throws MessagingException { throws MessagingException {
if (account.isMarkMessageAsReadOnView() && !message.isSet(Flag.SEEN)) { if (account.isMarkMessageAsReadOnView() && !message.isSet(Flag.SEEN)) {
List<Long> messageIds = Collections.singletonList(message.getId()); List<Long> messageIds = Collections.singletonList(message.getId());
setFlag(account, messageIds, Flag.SEEN, true); setFlag(account, messageIds, Flag.SEEN, true);
((LocalMessage) message).setFlagInternal(Flag.SEEN, true); message.setFlagInternal(Flag.SEEN, true);
} }
} }
@ -4013,7 +4020,7 @@ public class MessagingController implements Runnable {
@Override @Override
public void act(final Account account, final Folder folder, public void act(final Account account, final Folder folder,
final List<Message> accountMessages) { final List<Message> accountMessages) {
suppressMessages(account, messages); suppressMessages(account, messages);
putBackground("deleteMessages", null, new Runnable() { putBackground("deleteMessages", null, new Runnable() {

View File

@ -554,4 +554,8 @@ public class LocalMessage extends MimeMessage {
private String getAccountUuid() { private String getAccountUuid() {
return getAccount().getUuid(); return getAccount().getUuid();
} }
public boolean isBodyMissing() {
return getBody() == null;
}
} }

View File

@ -0,0 +1,60 @@
package com.fsck.k9.ui.message;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mailstore.LocalMessage;
public class LocalMessageLoader extends AsyncTaskLoader<LocalMessage> {
private final MessagingController controller;
private final Account account;
private final MessageReference messageReference;
private LocalMessage message;
public LocalMessageLoader(Context context, MessagingController controller, Account account,
MessageReference messageReference) {
super(context);
this.controller = controller;
this.account = account;
this.messageReference = messageReference;
}
@Override
protected void onStartLoading() {
if (message != null) {
super.deliverResult(message);
}
if (takeContentChanged() || message == null) {
forceLoad();
}
}
@Override
public void deliverResult(LocalMessage message) {
this.message = message;
super.deliverResult(message);
}
@Override
public LocalMessage loadInBackground() {
try {
return loadMessageFromDatabase();
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Error while loading message from database", e);
return null;
}
}
private LocalMessage loadMessageFromDatabase() throws MessagingException {
return controller.loadMessage(account, messageReference.folderName, messageReference.uid);
}
}

View File

@ -6,13 +6,16 @@ import java.util.Locale;
import android.app.Activity; import android.app.Activity;
import android.app.Fragment; import android.app.Fragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.Loader;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.app.DialogFragment; import android.app.DialogFragment;
import android.app.FragmentManager; import android.app.FragmentManager;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;
import android.view.KeyEvent; import android.view.KeyEvent;
@ -41,6 +44,7 @@ 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.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;
import com.fsck.k9.view.MessageHeader; import com.fsck.k9.view.MessageHeader;
@ -60,6 +64,8 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2; private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
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;
public static MessageViewFragment newInstance(MessageReference reference) { public static MessageViewFragment newInstance(MessageReference reference) {
MessageViewFragment fragment = new MessageViewFragment(); MessageViewFragment fragment = new MessageViewFragment();
@ -105,6 +111,8 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
private Context mContext; private Context mContext;
private LoaderCallbacks<LocalMessage> localMessageLoaderCallback = new LocalMessageLoaderCallback();
class MessageViewHandler extends Handler { class MessageViewHandler extends Handler {
@ -117,15 +125,6 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
}); });
} }
public void addAttachment(final View attachmentView) {
post(new Runnable() {
@Override
public void run() {
mMessageView.addAttachment(attachmentView);
}
});
}
/* A helper for a set of "show a toast" methods */ /* A helper for a set of "show a toast" methods */
private void showToast(final String message, final int toastLength) { private void showToast(final String message, final int toastLength) {
post(new Runnable() { post(new Runnable() {
@ -146,16 +145,6 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
showToast(context.getString(R.string.status_network_error), Toast.LENGTH_LONG); showToast(context.getString(R.string.status_network_error), Toast.LENGTH_LONG);
} }
public void invalidIdError() {
Context context = getActivity();
if (context == null) {
return;
}
showToast(context.getString(R.string.status_invalid_id_error), Toast.LENGTH_LONG);
}
public void fetchingAttachment() { public void fetchingAttachment() {
Context context = getActivity(); Context context = getActivity();
if (context == null) { if (context == null) {
@ -230,7 +219,12 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
}; };
}); });
mMessageView.initialize(this); mMessageView.initialize(this, new OnClickListener() {
@Override
public void onClick(View v) {
onToggleFlagged();
}
});
mMessageView.downloadRemainderButton().setOnClickListener(this); mMessageView.downloadRemainderButton().setOnClickListener(this);
mFragmentListener.messageHeaderViewAvailable(mMessageView.getMessageHeaderView()); mFragmentListener.messageHeaderViewAvailable(mMessageView.getMessageHeaderView());
@ -261,10 +255,6 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
outState.putSerializable(STATE_PGP_DATA, mPgpData); outState.putSerializable(STATE_PGP_DATA, mPgpData);
} }
public void displayMessage(MessageReference ref) {
displayMessage(ref, true);
}
private void displayMessage(MessageReference ref, boolean resetPgpData) { private void displayMessage(MessageReference ref, boolean resetPgpData) {
mMessageReference = ref; mMessageReference = ref;
if (K9.DEBUG) { if (K9.DEBUG) {
@ -283,11 +273,50 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
mMessageView.resetView(); mMessageView.resetView();
mMessageView.resetHeaderView(); mMessageView.resetHeaderView();
mController.loadMessageForView(mAccount, mMessageReference.folderName, mMessageReference.uid, mListener); startLoadingMessageFromDatabase();
mFragmentListener.updateMenu(); mFragmentListener.updateMenu();
} }
private void startLoadingMessageFromDatabase() {
getLoaderManager().initLoader(LOCAL_MESSAGE_LOADER_ID, null, localMessageLoaderCallback);
}
private void onLoadMessageFromDatabaseFinished(LocalMessage message) {
displayMessageHeader(message);
if (message.isBodyMissing()) {
startDownloadingMessageBody(message);
} else {
startExtractingTextAndAttachments(message);
}
}
private void onLoadMessageFromDatabaseFailed() {
mMessageView.showStatusMessage(mContext.getString(R.string.status_invalid_id_error));
}
private void startDownloadingMessageBody(LocalMessage message) {
throw new RuntimeException("Not implemented yet");
}
private void startExtractingTextAndAttachments(LocalMessage message) {
//TODO: extract in background thread
//TODO: handle decryption and signature verification
try {
mMessageView.setMessage(mAccount, message, mPgpData, mController, mListener);
mMessageView.setShowDownloadButton(message);
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Error while trying to display message", e);
}
}
private void displayMessageHeader(LocalMessage message) {
mMessageView.setHeaders(message, mAccount);
displayMessageSubject(getSubjectForMessage(message));
mFragmentListener.updateMenu();
}
/** /**
* Called from UI thread when user select Delete * Called from UI thread when user select Delete
*/ */
@ -516,6 +545,15 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
} }
} }
private String getSubjectForMessage(LocalMessage message) {
String subject = message.getSubject();
if (TextUtils.isEmpty(subject)) {
return mContext.getString(R.string.general_no_subject);
}
return subject;
}
public void moveMessage(MessageReference reference, String destFolderName) { public void moveMessage(MessageReference reference, String destFolderName) {
mController.moveMessage(mAccount, mMessageReference.folderName, mMessage, mController.moveMessage(mAccount, mMessageReference.folderName, mMessage,
destFolderName, null); destFolderName, null);
@ -530,126 +568,28 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
@Override @Override
public void loadMessageForViewHeadersAvailable(final Account account, String folder, String uid, public void loadMessageForViewHeadersAvailable(final Account account, String folder, String uid,
final Message message) { final Message message) {
if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder) throw new IllegalStateException();
|| !mMessageReference.accountUuid.equals(account.getUuid())) {
return;
}
/*
* Clone the message object because the original could be modified by
* MessagingController later. This could lead to a ConcurrentModificationException
* when that same object is accessed by the UI thread (below).
*
* See issue 3953
*
* This is just an ugly hack to get rid of the most pressing problem. A proper way to
* fix this is to make Message thread-safe. Or, even better, rewriting the UI code to
* access messages via a ContentProvider.
*
*/
final Message clonedMessage = message.clone();
mHandler.post(new Runnable() {
@Override
public void run() {
if (!clonedMessage.isSet(Flag.X_DOWNLOADED_FULL) &&
!clonedMessage.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
String text = mContext.getString(R.string.message_view_downloading);
mMessageView.showStatusMessage(text);
}
mMessageView.setHeaders(clonedMessage, account);
final String subject = clonedMessage.getSubject();
if (subject == null || subject.equals("")) {
displayMessageSubject(mContext.getString(R.string.general_no_subject));
} else {
displayMessageSubject(clonedMessage.getSubject());
}
mMessageView.setOnFlagListener(new OnClickListener() {
@Override
public void onClick(View v) {
onToggleFlagged();
}
});
}
});
} }
@Override @Override
public void loadMessageForViewBodyAvailable(final Account account, String folder, public void loadMessageForViewBodyAvailable(final Account account, String folder,
String uid, final Message message) { String uid, final Message message) {
if (!(message instanceof LocalMessage) || throw new IllegalStateException();
!mMessageReference.uid.equals(uid) ||
!mMessageReference.folderName.equals(folder) ||
!mMessageReference.accountUuid.equals(account.getUuid())) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
try {
mMessage = (LocalMessage) message;
mMessageView.setMessage(account, (LocalMessage) message, mPgpData,
mController, mListener);
mFragmentListener.updateMenu();
} catch (MessagingException e) {
Log.v(K9.LOG_TAG, "loadMessageForViewBodyAvailable", e);
}
}
});
} }
@Override @Override
public void loadMessageForViewFailed(Account account, String folder, String uid, final Throwable t) { public void loadMessageForViewFailed(Account account, String folder, String uid, final Throwable t) {
if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder) throw new IllegalStateException();
|| !mMessageReference.accountUuid.equals(account.getUuid())) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
setProgress(false);
if (t instanceof IllegalArgumentException) {
mHandler.invalidIdError();
} else {
mHandler.networkError();
}
if (mMessage == null || mMessage.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
mMessageView.showStatusMessage(
mContext.getString(R.string.webview_empty_message));
}
}
});
} }
@Override @Override
public void loadMessageForViewFinished(Account account, String folder, String uid, final Message message) { public void loadMessageForViewFinished(Account account, String folder, String uid, final Message message) {
if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder) throw new IllegalStateException();
|| !mMessageReference.accountUuid.equals(account.getUuid())) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
setProgress(false);
mMessageView.setShowDownloadButton(message);
}
});
} }
@Override @Override
public void loadMessageForViewStarted(Account account, String folder, String uid) { public void loadMessageForViewStarted(Account account, String folder, String uid) {
if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder) throw new IllegalStateException();
|| !mMessageReference.accountUuid.equals(account.getUuid())) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
setProgress(true);
}
});
} }
@Override @Override
@ -865,4 +805,26 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
public LayoutInflater getFragmentLayoutInflater() { public LayoutInflater getFragmentLayoutInflater() {
return mLayoutInflater; return mLayoutInflater;
} }
class LocalMessageLoaderCallback implements LoaderCallbacks<LocalMessage> {
@Override
public Loader<LocalMessage> onCreateLoader(int id, Bundle args) {
return new LocalMessageLoader(mContext, mController, mAccount, mMessageReference);
}
@Override
public void onLoadFinished(Loader<LocalMessage> loader, LocalMessage message) {
mMessage = message;
if (message == null) {
onLoadMessageFromDatabaseFailed();
} else {
onLoadMessageFromDatabaseFinished(message);
}
}
@Override
public void onLoaderReset(Loader<LocalMessage> loader) {
// Do nothing
}
}
} }

View File

@ -115,7 +115,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
private String mText; private String mText;
public void initialize(Fragment fragment) { public void initialize(Fragment fragment, OnClickListener flagListener) {
Activity activity = fragment.getActivity(); Activity activity = fragment.getActivity();
mMessageContentView = (MessageWebView) findViewById(R.id.message_content); mMessageContentView = (MessageWebView) findViewById(R.id.message_content);
mMessageContentView.configure(); mMessageContentView.configure();
@ -124,6 +124,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
mHeaderContainer = (MessageHeader) findViewById(R.id.header_container); mHeaderContainer = (MessageHeader) findViewById(R.id.header_container);
mHeaderContainer.setOnLayoutChangedListener(this); mHeaderContainer.setOnLayoutChangedListener(this);
mHeaderContainer.setOnFlagListener(flagListener);
mAttachmentsContainer = findViewById(R.id.attachments_container); mAttachmentsContainer = findViewById(R.id.attachments_container);
mAttachments = (LinearLayout) findViewById(R.id.attachments); mAttachments = (LinearLayout) findViewById(R.id.attachments);
@ -493,10 +494,6 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
} }
} }
public void setOnFlagListener(OnClickListener listener) {
mHeaderContainer.setOnFlagListener(listener);
}
public void showAllHeaders() { public void showAllHeaders() {
mHeaderContainer.onShowAdditionalHeaders(); mHeaderContainer.onShowAdditionalHeaders();
} }

View File

@ -153,8 +153,6 @@ public class MessageHeader extends LinearLayout implements OnClickListener {
} }
public void setOnFlagListener(OnClickListener listener) { public void setOnFlagListener(OnClickListener listener) {
if (mFlagged == null)
return;
mFlagged.setOnClickListener(listener); mFlagged.setOnClickListener(listener);
} }