Loading k9mail/src/main/java/com/fsck/k9/activity/MessageCompose.java +3 −5 Original line number Diff line number Diff line Loading @@ -1954,21 +1954,19 @@ public class MessageCompose extends K9Activity implements OnClickListener, } @Override public void loadMessageForViewFinished(Account account, String folder, String uid, LocalMessage message) { public void loadMessageForViewFinished(Account account, String folder, String uid) { if (mMessageReference == null || !mMessageReference.getUid().equals(uid)) { return; } mHandler.sendEmptyMessage(MSG_PROGRESS_OFF); } @Override public void loadMessageForViewBodyAvailable(Account account, String folder, String uid, final LocalMessage message) { if (mMessageReference == null || !mMessageReference.getUid().equals(uid)) { return; } final LocalMessage message = null; // TODO this isn't working at the moment! runOnUiThread(new Runnable() { @Override public void run() { Loading k9mail/src/main/java/com/fsck/k9/activity/MessageLoaderHelper.java 0 → 100644 +425 −0 Original line number Diff line number Diff line package com.fsck.k9.activity; import android.app.FragmentManager; import android.app.LoaderManager; import android.app.LoaderManager.LoaderCallbacks; import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.Loader; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.util.Log; import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.Preferences; import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.helper.RetainFragment; import com.fsck.k9.mail.Flag; import com.fsck.k9.mailstore.LocalMessage; import com.fsck.k9.mailstore.MessageViewInfo; import com.fsck.k9.ui.crypto.MessageCryptoAnnotations; import com.fsck.k9.ui.crypto.MessageCryptoCallback; import com.fsck.k9.ui.crypto.MessageCryptoHelper; import com.fsck.k9.ui.message.LocalMessageExtractorLoader; import com.fsck.k9.ui.message.LocalMessageLoader; /** This class is responsible for loading a message start to finish, and * retaining or reloading the loading state on configuration changes. * * In particular, it takes care of the following: * - load raw message data from the database, using LocalMessageLoader * - download partial message content if it is missing using MessagingController * - apply crypto operations if applicable, using MessageCryptoHelper * - extract MessageViewInfo from the message and crypto data using DecodeMessageLoader * - download complete message content for partially downloaded messages if requested * * No state is retained in this object itself. Instead, state is stored in the * message loaders and the MessageCryptoHelper which is stored in a * RetainFragment. The public interface is intended for use by an Activity or * Fragment, which should construct a new instance of this class in onCreate, * then call asyncStartOrResumeLoadingMessage to start or resume loading the * message, receiving callbacks when it is loaded. * * When the Activity or Fragment is ultimately destroyed, it should call * onDestroy, which stops loading and deletes all state kept in loaders and * fragments by this object. If it is only destroyed for a configuration * change, it should call onDestroyChangingConfigurations, which cancels any * further callbacks from this object but retains the loading state to resume * from at the next call to asyncStartOrResumeLoadingMessage. * * If the message is already loaded, a call to asyncStartOrResumeLoadingMessage * will typically load by starting the decode message loader, retrieving the * already cached LocalMessage. This message will be passed to the retained * CryptoMessageHelper instance, returning the already cached * MessageCryptoAnnotations. These two objects will be checked against the * retained DecodeMessageLoader, returning the final result. At each * intermediate step, the input of the respective loaders will be checked for * consistency, reloading if there is a mismatch. * */ public class MessageLoaderHelper { private static final int LOCAL_MESSAGE_LOADER_ID = 1; private static final int DECODE_MESSAGE_LOADER_ID = 2; // injected state private final Context context; private final FragmentManager fragmentManager; private final LoaderManager loaderManager; @Nullable // may be cleared private MessageLoaderCallbacks callback; // transient state private MessageReference messageReference; private Account account; private LocalMessage localMessage; private MessageCryptoAnnotations messageCryptoAnnotations; private MessageCryptoHelper messageCryptoHelper; public MessageLoaderHelper(Context context, LoaderManager loaderManager, FragmentManager fragmentManager, @NonNull MessageLoaderCallbacks callback) { this.context = context; this.loaderManager = loaderManager; this.fragmentManager = fragmentManager; this.callback = callback; } // public interface @UiThread public void asyncStartOrResumeLoadingMessage(MessageReference messageReference) { this.messageReference = messageReference; this.account = Preferences.getPreferences(context).getAccount(messageReference.getAccountUuid()); startOrResumeLocalMessageLoader(); } /** Cancels all loading processes, prevents future callbacks, and destroys all loading state. */ @UiThread public void onDestroy() { if (messageCryptoHelper != null) { messageCryptoHelper.cancelIfRunning(); } callback = null; } /** Prevents future callbacks, but retains loading state to pick up from in a call to * asyncStartOrResumeLoadingMessage in a new instance of this class. */ @UiThread public void onDestroyChangingConfigurations() { if (messageCryptoHelper != null) { messageCryptoHelper.detachCallback(); } callback = null; } @UiThread public void downloadCompleteMessage() { if (localMessage.isSet(Flag.X_DOWNLOADED_FULL)) { return; } startDownloadingMessageBody(true); } @UiThread public void restartMessageCryptoProcessing() { cancelAndClearCryptoOperation(); cancelAndClearDecodeLoader(); startOrResumeCryptoOperation(); } @UiThread public void onActivityResult(int requestCode, int resultCode, Intent data) { messageCryptoHelper.onActivityResult(requestCode, resultCode, data); } // load from database private void startOrResumeLocalMessageLoader() { LocalMessageLoader loader = (LocalMessageLoader) loaderManager.<LocalMessage>getLoader(LOCAL_MESSAGE_LOADER_ID); boolean isLoaderStale = (loader == null) || !loader.isCreatedFor(messageReference); if (isLoaderStale) { Log.d(K9.LOG_TAG, "Creating new local message loader"); cancelAndClearCryptoOperation(); cancelAndClearDecodeLoader(); loaderManager.restartLoader(LOCAL_MESSAGE_LOADER_ID, null, localMessageLoaderCallback); } else { Log.d(K9.LOG_TAG, "Reusing local message loader"); loaderManager.initLoader(LOCAL_MESSAGE_LOADER_ID, null, localMessageLoaderCallback); } } @UiThread private void onLoadMessageFromDatabaseFinished() { if (callback == null) { throw new IllegalStateException("unexpected call when callback is already detached"); } callback.onMessageDataLoadFinished(localMessage); if (localMessage.isBodyMissing()) { startDownloadingMessageBody(false); return; } if (account.isOpenPgpProviderConfigured()) { startOrResumeCryptoOperation(); return; } startOrResumeDecodeMessage(); } private void onLoadMessageFromDatabaseFailed() { if (callback == null) { throw new IllegalStateException("unexpected call when callback is already detached"); } callback.onMessageDataLoadFailed(); } private void cancelAndClearLocalMessageLoader() { loaderManager.destroyLoader(LOCAL_MESSAGE_LOADER_ID); } private LoaderCallbacks<LocalMessage> localMessageLoaderCallback = new LoaderCallbacks<LocalMessage>() { @Override public Loader<LocalMessage> onCreateLoader(int id, Bundle args) { if (id != LOCAL_MESSAGE_LOADER_ID) { throw new IllegalStateException("loader id must be message loader id"); } return new LocalMessageLoader(context, MessagingController.getInstance(context), account, messageReference); } @Override public void onLoadFinished(Loader<LocalMessage> loader, LocalMessage message) { if (loader.getId() != LOCAL_MESSAGE_LOADER_ID) { throw new IllegalStateException("loader id must be message loader id"); } localMessage = message; if (message == null) { onLoadMessageFromDatabaseFailed(); } else { onLoadMessageFromDatabaseFinished(); } } @Override public void onLoaderReset(Loader<LocalMessage> loader) { if (loader.getId() != LOCAL_MESSAGE_LOADER_ID) { throw new IllegalStateException("loader id must be message loader id"); } // Do nothing } }; // process with crypto helper private void startOrResumeCryptoOperation() { RetainFragment<MessageCryptoHelper> retainCryptoHelperFragment = getMessageCryptoHelperRetainFragment(); if (retainCryptoHelperFragment.hasData()) { messageCryptoHelper = retainCryptoHelperFragment.getData(); } else { messageCryptoHelper = new MessageCryptoHelper(context, account.getOpenPgpProvider()); retainCryptoHelperFragment.setData(messageCryptoHelper); } messageCryptoHelper.asyncStartOrResumeProcessingMessage(localMessage, messageCryptoCallback); } private void cancelAndClearCryptoOperation() { RetainFragment<MessageCryptoHelper> retainCryptoHelperFragment = getMessageCryptoHelperRetainFragment(); if (retainCryptoHelperFragment != null) { if (retainCryptoHelperFragment.hasData()) { messageCryptoHelper = retainCryptoHelperFragment.getData(); messageCryptoHelper.cancelIfRunning(); messageCryptoHelper = null; } retainCryptoHelperFragment.clearAndRemove(fragmentManager); } } private RetainFragment<MessageCryptoHelper> getMessageCryptoHelperRetainFragment() { return RetainFragment.findOrCreate(fragmentManager, "crypto_helper_" + messageReference.hashCode()); } private MessageCryptoCallback messageCryptoCallback = new MessageCryptoCallback() { @Override public void onCryptoHelperProgress(int current, int max) { if (callback == null) { throw new IllegalStateException("unexpected call when callback is already detached"); } callback.setLoadingProgress(current, max); } @Override public void onCryptoOperationsFinished(MessageCryptoAnnotations annotations) { if (callback == null) { throw new IllegalStateException("unexpected call when callback is already detached"); } messageCryptoAnnotations = annotations; startOrResumeDecodeMessage(); } @Override public void startPendingIntentForCryptoHelper(IntentSender si, int requestCode, Intent fillIntent, int flagsMask, int flagValues, int extraFlags) { if (callback == null) { throw new IllegalStateException("unexpected call when callback is already detached"); } callback.startIntentSenderForMessageLoaderHelper(si, requestCode, fillIntent, flagsMask, flagValues, extraFlags); } }; // decode message private void startOrResumeDecodeMessage() { LocalMessageExtractorLoader loader = (LocalMessageExtractorLoader) loaderManager.<MessageViewInfo>getLoader(DECODE_MESSAGE_LOADER_ID); boolean isLoaderStale = (loader == null) || !loader.isCreatedFor(localMessage, messageCryptoAnnotations); if (isLoaderStale) { Log.d(K9.LOG_TAG, "Creating new decode message loader"); loaderManager.restartLoader(DECODE_MESSAGE_LOADER_ID, null, decodeMessageLoaderCallback); } else { Log.d(K9.LOG_TAG, "Reusing decode message loader"); loaderManager.initLoader(DECODE_MESSAGE_LOADER_ID, null, decodeMessageLoaderCallback); } } private void onDecodeMessageFinished(MessageViewInfo messageViewInfo) { if (callback == null) { throw new IllegalStateException("unexpected call when callback is already detached"); } if (messageViewInfo == null) { callback.onMessageViewInfoLoadFailed(localMessage); return; } callback.onMessageViewInfoLoadFinished(localMessage, messageViewInfo); } private void cancelAndClearDecodeLoader() { loaderManager.destroyLoader(DECODE_MESSAGE_LOADER_ID); } private LoaderCallbacks<MessageViewInfo> decodeMessageLoaderCallback = new LoaderCallbacks<MessageViewInfo>() { @Override public Loader<MessageViewInfo> onCreateLoader(int id, Bundle args) { if (id != DECODE_MESSAGE_LOADER_ID) { throw new IllegalStateException("loader id must be message decoder id"); } return new LocalMessageExtractorLoader(context, localMessage, messageCryptoAnnotations); } @Override public void onLoadFinished(Loader<MessageViewInfo> loader, MessageViewInfo messageViewInfo) { if (loader.getId() != DECODE_MESSAGE_LOADER_ID) { throw new IllegalStateException("loader id must be message decoder id"); } onDecodeMessageFinished(messageViewInfo); } @Override public void onLoaderReset(Loader<MessageViewInfo> loader) { if (loader.getId() != DECODE_MESSAGE_LOADER_ID) { throw new IllegalStateException("loader id must be message decoder id"); } // Do nothing } }; // download missing body private void startDownloadingMessageBody(boolean downloadComplete) { if (downloadComplete) { MessagingController.getInstance(context).loadMessageRemote( account, messageReference.getFolderName(), messageReference.getUid(), downloadMessageListener); } else { MessagingController.getInstance(context).loadMessageRemotePartial( account, messageReference.getFolderName(), messageReference.getUid(), downloadMessageListener); } } private void onMessageDownloadFinished() { if (callback == null) { return; } cancelAndClearLocalMessageLoader(); cancelAndClearDecodeLoader(); cancelAndClearCryptoOperation(); startOrResumeLocalMessageLoader(); } private void onDownloadMessageFailed(final Throwable t) { if (callback == null) { return; } if (t instanceof IllegalArgumentException) { callback.onDownloadErrorMessageNotFound(); } else { callback.onDownloadErrorNetworkError(); } } MessagingListener downloadMessageListener = new MessagingListener() { @Override public void loadMessageRemoteFinished(Account account, String folder, String uid) { onMessageDownloadFinished(); } @Override public void loadMessageRemoteFailed(Account account, String folder, String uid, final Throwable t) { onDownloadMessageFailed(t); } }; // callback interface public interface MessageLoaderCallbacks { void onMessageDataLoadFinished(LocalMessage message); void onMessageDataLoadFailed(); void onMessageViewInfoLoadFinished(LocalMessage localMessage, MessageViewInfo messageViewInfo); void onMessageViewInfoLoadFailed(LocalMessage localMessage); void setLoadingProgress(int current, int max); void startIntentSenderForMessageLoaderHelper(IntentSender si, int requestCode, Intent fillIntent, int flagsMask, int flagValues, int extraFlags); void onDownloadErrorMessageNotFound(); void onDownloadErrorNetworkError(); } } k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java +54 −95 Original line number Diff line number Diff line Loading @@ -2696,28 +2696,28 @@ public class MessagingController implements Runnable { } } public void loadMessagePartialForViewRemote(final Account account, final String folder, public void loadMessageRemotePartial(final Account account, final String folder, final String uid, final MessagingListener listener) { put("loadMessageForViewRemote", listener, new Runnable() { put("loadMessageRemotePartial", listener, new Runnable() { @Override public void run() { loadMessageForViewRemoteSynchronous(account, folder, uid, listener, true); loadMessageRemoteSynchronous(account, folder, uid, listener, true); } }); } //TODO: Fix the callback mess. See GH-782 public void loadMessageForViewRemote(final Account account, final String folder, public void loadMessageRemote(final Account account, final String folder, final String uid, final MessagingListener listener) { put("loadMessageForViewRemote", listener, new Runnable() { put("loadMessageRemote", listener, new Runnable() { @Override public void run() { loadMessageForViewRemoteSynchronous(account, folder, uid, listener, false); loadMessageRemoteSynchronous(account, folder, uid, listener, false); } }); } public boolean loadMessageForViewRemoteSynchronous(final Account account, final String folder, public boolean loadMessageRemoteSynchronous(final Account account, final String folder, final String uid, final MessagingListener listener, final boolean loadPartialFromSearch) { Folder remoteFolder = null; LocalFolder localFolder = null; Loading Loading @@ -2750,16 +2750,7 @@ public class MessagingController implements Runnable { message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false); }*/ if (message.isSet(Flag.X_DOWNLOADED_FULL)) { /* * If the message has been synchronized since we were called we'll * just hand it back cause it's ready to go. */ FetchProfile fp = new FetchProfile(); fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.BODY); localFolder.fetch(Collections.singletonList(message), fp, null); } else { if (!message.isSet(Flag.X_DOWNLOADED_FULL)) { /* * At this point the message is not available, so we need to download it * fully if possible. Loading @@ -2784,36 +2775,25 @@ public class MessagingController implements Runnable { message = localFolder.getMessage(uid); FetchProfile fp = new FetchProfile(); fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.BODY); localFolder.fetch(Collections.singletonList(message), fp, null); if (!loadPartialFromSearch) { message.setFlag(Flag.X_DOWNLOADED_FULL, true); } } // Mark that this message is now fully synched if (account.isMarkMessageAsReadOnView()) { message.setFlag(Flag.SEEN, true); } } // now that we have the full message, refresh the headers for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewHeadersAvailable(account, folder, uid, message); l.loadMessageRemoteFinished(account, folder, uid); } for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewBodyAvailable(account, folder, uid, message); } for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewFinished(account, folder, uid, message); } return true; } catch (Exception e) { for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewFailed(account, folder, uid, e); l.loadMessageRemoteFailed(account, folder, uid, e); } notifyUserIfCertificateProblem(account, e, true); addErrorMessage(account, null, e); Loading Loading @@ -2847,17 +2827,12 @@ public class MessagingController implements Runnable { // TODO: limit by account.getMaximumAutoDownloadMessageSize(). if (!message.isSet(Flag.X_DOWNLOADED_FULL) && !message.isSet(Flag.X_DOWNLOADED_PARTIAL)) { if (loadMessageForViewRemoteSynchronous(account, folder, uid, listener, true)) { if (loadMessageRemoteSynchronous(account, folder, uid, listener, true)) { markMessageAsReadOnView(account, message); } return; } for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewHeadersAvailable(account, folder, uid, message); } FetchProfile fp = new FetchProfile(); fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.BODY); Loading @@ -2865,11 +2840,7 @@ public class MessagingController implements Runnable { localFolder.close(); for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewBodyAvailable(account, folder, uid, message); } for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewFinished(account, folder, uid, message); l.loadMessageForViewFinished(account, folder, uid); } markMessageAsReadOnView(account, message); Loading Loading @@ -3876,18 +3847,9 @@ public class MessagingController implements Runnable { return (account.getRemoteStore() instanceof Pop3Store); } public void sendAlternate(final Context context, Account account, Message message) { if (K9.DEBUG) Log.d(K9.LOG_TAG, "About to load message " + account.getDescription() + ":" + message.getFolder().getName() + ":" + message.getUid() + " for sendAlternate"); loadMessageForView(account, message.getFolder().getName(), message.getUid(), new MessagingListener() { @Override public void loadMessageForViewBodyAvailable(Account account, String folder, String uid, LocalMessage message) { public void sendAlternate(Context context, Account account, LocalMessage message) { if (K9.DEBUG) Log.d(K9.LOG_TAG, "Got message " + account.getDescription() + ":" + folder Log.d(K9.LOG_TAG, "Got message " + account.getDescription() + ":" + message.getFolder() + ":" + message.getUid() + " for sendAlternate"); try { Loading Loading @@ -3932,9 +3894,6 @@ public class MessagingController implements Runnable { Log.e(K9.LOG_TAG, "Unable to send email through alternate program", me); } } }); } /** * Checks mail for one or multiple accounts. If account is null all accounts Loading k9mail/src/main/java/com/fsck/k9/controller/MessagingListener.java +2 −18 Original line number Diff line number Diff line Loading @@ -81,27 +81,11 @@ public class MessagingListener { public void synchronizeMailboxFailed(Account account, String folder, String message) {} public void loadMessageRemoteFinished(Account account, String folder, String uid) {} public void loadMessageForViewStarted(Account account, String folder, String uid) {} public void loadMessageForViewHeadersAvailable(Account account, String folder, String uid, Message message) {} public void loadMessageForViewBodyAvailable(Account account, String folder, String uid, LocalMessage message) {} public void loadMessageForViewFinished(Account account, String folder, String uid, LocalMessage message) {} public void loadMessageForViewFailed(Account account, String folder, String uid, public void loadMessageRemoteFailed(Account account, String folder, String uid, Throwable t) {} /** * Called when a message for view has been fully displayed on the screen. */ public void messageViewFinished() {} public void checkMailStarted(Context context, Account account) {} public void checkMailFinished(Context context, Account account) {} Loading k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java +4 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
k9mail/src/main/java/com/fsck/k9/activity/MessageCompose.java +3 −5 Original line number Diff line number Diff line Loading @@ -1954,21 +1954,19 @@ public class MessageCompose extends K9Activity implements OnClickListener, } @Override public void loadMessageForViewFinished(Account account, String folder, String uid, LocalMessage message) { public void loadMessageForViewFinished(Account account, String folder, String uid) { if (mMessageReference == null || !mMessageReference.getUid().equals(uid)) { return; } mHandler.sendEmptyMessage(MSG_PROGRESS_OFF); } @Override public void loadMessageForViewBodyAvailable(Account account, String folder, String uid, final LocalMessage message) { if (mMessageReference == null || !mMessageReference.getUid().equals(uid)) { return; } final LocalMessage message = null; // TODO this isn't working at the moment! runOnUiThread(new Runnable() { @Override public void run() { Loading
k9mail/src/main/java/com/fsck/k9/activity/MessageLoaderHelper.java 0 → 100644 +425 −0 Original line number Diff line number Diff line package com.fsck.k9.activity; import android.app.FragmentManager; import android.app.LoaderManager; import android.app.LoaderManager.LoaderCallbacks; import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.Loader; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.util.Log; import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.Preferences; import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.helper.RetainFragment; import com.fsck.k9.mail.Flag; import com.fsck.k9.mailstore.LocalMessage; import com.fsck.k9.mailstore.MessageViewInfo; import com.fsck.k9.ui.crypto.MessageCryptoAnnotations; import com.fsck.k9.ui.crypto.MessageCryptoCallback; import com.fsck.k9.ui.crypto.MessageCryptoHelper; import com.fsck.k9.ui.message.LocalMessageExtractorLoader; import com.fsck.k9.ui.message.LocalMessageLoader; /** This class is responsible for loading a message start to finish, and * retaining or reloading the loading state on configuration changes. * * In particular, it takes care of the following: * - load raw message data from the database, using LocalMessageLoader * - download partial message content if it is missing using MessagingController * - apply crypto operations if applicable, using MessageCryptoHelper * - extract MessageViewInfo from the message and crypto data using DecodeMessageLoader * - download complete message content for partially downloaded messages if requested * * No state is retained in this object itself. Instead, state is stored in the * message loaders and the MessageCryptoHelper which is stored in a * RetainFragment. The public interface is intended for use by an Activity or * Fragment, which should construct a new instance of this class in onCreate, * then call asyncStartOrResumeLoadingMessage to start or resume loading the * message, receiving callbacks when it is loaded. * * When the Activity or Fragment is ultimately destroyed, it should call * onDestroy, which stops loading and deletes all state kept in loaders and * fragments by this object. If it is only destroyed for a configuration * change, it should call onDestroyChangingConfigurations, which cancels any * further callbacks from this object but retains the loading state to resume * from at the next call to asyncStartOrResumeLoadingMessage. * * If the message is already loaded, a call to asyncStartOrResumeLoadingMessage * will typically load by starting the decode message loader, retrieving the * already cached LocalMessage. This message will be passed to the retained * CryptoMessageHelper instance, returning the already cached * MessageCryptoAnnotations. These two objects will be checked against the * retained DecodeMessageLoader, returning the final result. At each * intermediate step, the input of the respective loaders will be checked for * consistency, reloading if there is a mismatch. * */ public class MessageLoaderHelper { private static final int LOCAL_MESSAGE_LOADER_ID = 1; private static final int DECODE_MESSAGE_LOADER_ID = 2; // injected state private final Context context; private final FragmentManager fragmentManager; private final LoaderManager loaderManager; @Nullable // may be cleared private MessageLoaderCallbacks callback; // transient state private MessageReference messageReference; private Account account; private LocalMessage localMessage; private MessageCryptoAnnotations messageCryptoAnnotations; private MessageCryptoHelper messageCryptoHelper; public MessageLoaderHelper(Context context, LoaderManager loaderManager, FragmentManager fragmentManager, @NonNull MessageLoaderCallbacks callback) { this.context = context; this.loaderManager = loaderManager; this.fragmentManager = fragmentManager; this.callback = callback; } // public interface @UiThread public void asyncStartOrResumeLoadingMessage(MessageReference messageReference) { this.messageReference = messageReference; this.account = Preferences.getPreferences(context).getAccount(messageReference.getAccountUuid()); startOrResumeLocalMessageLoader(); } /** Cancels all loading processes, prevents future callbacks, and destroys all loading state. */ @UiThread public void onDestroy() { if (messageCryptoHelper != null) { messageCryptoHelper.cancelIfRunning(); } callback = null; } /** Prevents future callbacks, but retains loading state to pick up from in a call to * asyncStartOrResumeLoadingMessage in a new instance of this class. */ @UiThread public void onDestroyChangingConfigurations() { if (messageCryptoHelper != null) { messageCryptoHelper.detachCallback(); } callback = null; } @UiThread public void downloadCompleteMessage() { if (localMessage.isSet(Flag.X_DOWNLOADED_FULL)) { return; } startDownloadingMessageBody(true); } @UiThread public void restartMessageCryptoProcessing() { cancelAndClearCryptoOperation(); cancelAndClearDecodeLoader(); startOrResumeCryptoOperation(); } @UiThread public void onActivityResult(int requestCode, int resultCode, Intent data) { messageCryptoHelper.onActivityResult(requestCode, resultCode, data); } // load from database private void startOrResumeLocalMessageLoader() { LocalMessageLoader loader = (LocalMessageLoader) loaderManager.<LocalMessage>getLoader(LOCAL_MESSAGE_LOADER_ID); boolean isLoaderStale = (loader == null) || !loader.isCreatedFor(messageReference); if (isLoaderStale) { Log.d(K9.LOG_TAG, "Creating new local message loader"); cancelAndClearCryptoOperation(); cancelAndClearDecodeLoader(); loaderManager.restartLoader(LOCAL_MESSAGE_LOADER_ID, null, localMessageLoaderCallback); } else { Log.d(K9.LOG_TAG, "Reusing local message loader"); loaderManager.initLoader(LOCAL_MESSAGE_LOADER_ID, null, localMessageLoaderCallback); } } @UiThread private void onLoadMessageFromDatabaseFinished() { if (callback == null) { throw new IllegalStateException("unexpected call when callback is already detached"); } callback.onMessageDataLoadFinished(localMessage); if (localMessage.isBodyMissing()) { startDownloadingMessageBody(false); return; } if (account.isOpenPgpProviderConfigured()) { startOrResumeCryptoOperation(); return; } startOrResumeDecodeMessage(); } private void onLoadMessageFromDatabaseFailed() { if (callback == null) { throw new IllegalStateException("unexpected call when callback is already detached"); } callback.onMessageDataLoadFailed(); } private void cancelAndClearLocalMessageLoader() { loaderManager.destroyLoader(LOCAL_MESSAGE_LOADER_ID); } private LoaderCallbacks<LocalMessage> localMessageLoaderCallback = new LoaderCallbacks<LocalMessage>() { @Override public Loader<LocalMessage> onCreateLoader(int id, Bundle args) { if (id != LOCAL_MESSAGE_LOADER_ID) { throw new IllegalStateException("loader id must be message loader id"); } return new LocalMessageLoader(context, MessagingController.getInstance(context), account, messageReference); } @Override public void onLoadFinished(Loader<LocalMessage> loader, LocalMessage message) { if (loader.getId() != LOCAL_MESSAGE_LOADER_ID) { throw new IllegalStateException("loader id must be message loader id"); } localMessage = message; if (message == null) { onLoadMessageFromDatabaseFailed(); } else { onLoadMessageFromDatabaseFinished(); } } @Override public void onLoaderReset(Loader<LocalMessage> loader) { if (loader.getId() != LOCAL_MESSAGE_LOADER_ID) { throw new IllegalStateException("loader id must be message loader id"); } // Do nothing } }; // process with crypto helper private void startOrResumeCryptoOperation() { RetainFragment<MessageCryptoHelper> retainCryptoHelperFragment = getMessageCryptoHelperRetainFragment(); if (retainCryptoHelperFragment.hasData()) { messageCryptoHelper = retainCryptoHelperFragment.getData(); } else { messageCryptoHelper = new MessageCryptoHelper(context, account.getOpenPgpProvider()); retainCryptoHelperFragment.setData(messageCryptoHelper); } messageCryptoHelper.asyncStartOrResumeProcessingMessage(localMessage, messageCryptoCallback); } private void cancelAndClearCryptoOperation() { RetainFragment<MessageCryptoHelper> retainCryptoHelperFragment = getMessageCryptoHelperRetainFragment(); if (retainCryptoHelperFragment != null) { if (retainCryptoHelperFragment.hasData()) { messageCryptoHelper = retainCryptoHelperFragment.getData(); messageCryptoHelper.cancelIfRunning(); messageCryptoHelper = null; } retainCryptoHelperFragment.clearAndRemove(fragmentManager); } } private RetainFragment<MessageCryptoHelper> getMessageCryptoHelperRetainFragment() { return RetainFragment.findOrCreate(fragmentManager, "crypto_helper_" + messageReference.hashCode()); } private MessageCryptoCallback messageCryptoCallback = new MessageCryptoCallback() { @Override public void onCryptoHelperProgress(int current, int max) { if (callback == null) { throw new IllegalStateException("unexpected call when callback is already detached"); } callback.setLoadingProgress(current, max); } @Override public void onCryptoOperationsFinished(MessageCryptoAnnotations annotations) { if (callback == null) { throw new IllegalStateException("unexpected call when callback is already detached"); } messageCryptoAnnotations = annotations; startOrResumeDecodeMessage(); } @Override public void startPendingIntentForCryptoHelper(IntentSender si, int requestCode, Intent fillIntent, int flagsMask, int flagValues, int extraFlags) { if (callback == null) { throw new IllegalStateException("unexpected call when callback is already detached"); } callback.startIntentSenderForMessageLoaderHelper(si, requestCode, fillIntent, flagsMask, flagValues, extraFlags); } }; // decode message private void startOrResumeDecodeMessage() { LocalMessageExtractorLoader loader = (LocalMessageExtractorLoader) loaderManager.<MessageViewInfo>getLoader(DECODE_MESSAGE_LOADER_ID); boolean isLoaderStale = (loader == null) || !loader.isCreatedFor(localMessage, messageCryptoAnnotations); if (isLoaderStale) { Log.d(K9.LOG_TAG, "Creating new decode message loader"); loaderManager.restartLoader(DECODE_MESSAGE_LOADER_ID, null, decodeMessageLoaderCallback); } else { Log.d(K9.LOG_TAG, "Reusing decode message loader"); loaderManager.initLoader(DECODE_MESSAGE_LOADER_ID, null, decodeMessageLoaderCallback); } } private void onDecodeMessageFinished(MessageViewInfo messageViewInfo) { if (callback == null) { throw new IllegalStateException("unexpected call when callback is already detached"); } if (messageViewInfo == null) { callback.onMessageViewInfoLoadFailed(localMessage); return; } callback.onMessageViewInfoLoadFinished(localMessage, messageViewInfo); } private void cancelAndClearDecodeLoader() { loaderManager.destroyLoader(DECODE_MESSAGE_LOADER_ID); } private LoaderCallbacks<MessageViewInfo> decodeMessageLoaderCallback = new LoaderCallbacks<MessageViewInfo>() { @Override public Loader<MessageViewInfo> onCreateLoader(int id, Bundle args) { if (id != DECODE_MESSAGE_LOADER_ID) { throw new IllegalStateException("loader id must be message decoder id"); } return new LocalMessageExtractorLoader(context, localMessage, messageCryptoAnnotations); } @Override public void onLoadFinished(Loader<MessageViewInfo> loader, MessageViewInfo messageViewInfo) { if (loader.getId() != DECODE_MESSAGE_LOADER_ID) { throw new IllegalStateException("loader id must be message decoder id"); } onDecodeMessageFinished(messageViewInfo); } @Override public void onLoaderReset(Loader<MessageViewInfo> loader) { if (loader.getId() != DECODE_MESSAGE_LOADER_ID) { throw new IllegalStateException("loader id must be message decoder id"); } // Do nothing } }; // download missing body private void startDownloadingMessageBody(boolean downloadComplete) { if (downloadComplete) { MessagingController.getInstance(context).loadMessageRemote( account, messageReference.getFolderName(), messageReference.getUid(), downloadMessageListener); } else { MessagingController.getInstance(context).loadMessageRemotePartial( account, messageReference.getFolderName(), messageReference.getUid(), downloadMessageListener); } } private void onMessageDownloadFinished() { if (callback == null) { return; } cancelAndClearLocalMessageLoader(); cancelAndClearDecodeLoader(); cancelAndClearCryptoOperation(); startOrResumeLocalMessageLoader(); } private void onDownloadMessageFailed(final Throwable t) { if (callback == null) { return; } if (t instanceof IllegalArgumentException) { callback.onDownloadErrorMessageNotFound(); } else { callback.onDownloadErrorNetworkError(); } } MessagingListener downloadMessageListener = new MessagingListener() { @Override public void loadMessageRemoteFinished(Account account, String folder, String uid) { onMessageDownloadFinished(); } @Override public void loadMessageRemoteFailed(Account account, String folder, String uid, final Throwable t) { onDownloadMessageFailed(t); } }; // callback interface public interface MessageLoaderCallbacks { void onMessageDataLoadFinished(LocalMessage message); void onMessageDataLoadFailed(); void onMessageViewInfoLoadFinished(LocalMessage localMessage, MessageViewInfo messageViewInfo); void onMessageViewInfoLoadFailed(LocalMessage localMessage); void setLoadingProgress(int current, int max); void startIntentSenderForMessageLoaderHelper(IntentSender si, int requestCode, Intent fillIntent, int flagsMask, int flagValues, int extraFlags); void onDownloadErrorMessageNotFound(); void onDownloadErrorNetworkError(); } }
k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java +54 −95 Original line number Diff line number Diff line Loading @@ -2696,28 +2696,28 @@ public class MessagingController implements Runnable { } } public void loadMessagePartialForViewRemote(final Account account, final String folder, public void loadMessageRemotePartial(final Account account, final String folder, final String uid, final MessagingListener listener) { put("loadMessageForViewRemote", listener, new Runnable() { put("loadMessageRemotePartial", listener, new Runnable() { @Override public void run() { loadMessageForViewRemoteSynchronous(account, folder, uid, listener, true); loadMessageRemoteSynchronous(account, folder, uid, listener, true); } }); } //TODO: Fix the callback mess. See GH-782 public void loadMessageForViewRemote(final Account account, final String folder, public void loadMessageRemote(final Account account, final String folder, final String uid, final MessagingListener listener) { put("loadMessageForViewRemote", listener, new Runnable() { put("loadMessageRemote", listener, new Runnable() { @Override public void run() { loadMessageForViewRemoteSynchronous(account, folder, uid, listener, false); loadMessageRemoteSynchronous(account, folder, uid, listener, false); } }); } public boolean loadMessageForViewRemoteSynchronous(final Account account, final String folder, public boolean loadMessageRemoteSynchronous(final Account account, final String folder, final String uid, final MessagingListener listener, final boolean loadPartialFromSearch) { Folder remoteFolder = null; LocalFolder localFolder = null; Loading Loading @@ -2750,16 +2750,7 @@ public class MessagingController implements Runnable { message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false); }*/ if (message.isSet(Flag.X_DOWNLOADED_FULL)) { /* * If the message has been synchronized since we were called we'll * just hand it back cause it's ready to go. */ FetchProfile fp = new FetchProfile(); fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.BODY); localFolder.fetch(Collections.singletonList(message), fp, null); } else { if (!message.isSet(Flag.X_DOWNLOADED_FULL)) { /* * At this point the message is not available, so we need to download it * fully if possible. Loading @@ -2784,36 +2775,25 @@ public class MessagingController implements Runnable { message = localFolder.getMessage(uid); FetchProfile fp = new FetchProfile(); fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.BODY); localFolder.fetch(Collections.singletonList(message), fp, null); if (!loadPartialFromSearch) { message.setFlag(Flag.X_DOWNLOADED_FULL, true); } } // Mark that this message is now fully synched if (account.isMarkMessageAsReadOnView()) { message.setFlag(Flag.SEEN, true); } } // now that we have the full message, refresh the headers for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewHeadersAvailable(account, folder, uid, message); l.loadMessageRemoteFinished(account, folder, uid); } for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewBodyAvailable(account, folder, uid, message); } for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewFinished(account, folder, uid, message); } return true; } catch (Exception e) { for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewFailed(account, folder, uid, e); l.loadMessageRemoteFailed(account, folder, uid, e); } notifyUserIfCertificateProblem(account, e, true); addErrorMessage(account, null, e); Loading Loading @@ -2847,17 +2827,12 @@ public class MessagingController implements Runnable { // TODO: limit by account.getMaximumAutoDownloadMessageSize(). if (!message.isSet(Flag.X_DOWNLOADED_FULL) && !message.isSet(Flag.X_DOWNLOADED_PARTIAL)) { if (loadMessageForViewRemoteSynchronous(account, folder, uid, listener, true)) { if (loadMessageRemoteSynchronous(account, folder, uid, listener, true)) { markMessageAsReadOnView(account, message); } return; } for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewHeadersAvailable(account, folder, uid, message); } FetchProfile fp = new FetchProfile(); fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.BODY); Loading @@ -2865,11 +2840,7 @@ public class MessagingController implements Runnable { localFolder.close(); for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewBodyAvailable(account, folder, uid, message); } for (MessagingListener l : getListeners(listener)) { l.loadMessageForViewFinished(account, folder, uid, message); l.loadMessageForViewFinished(account, folder, uid); } markMessageAsReadOnView(account, message); Loading Loading @@ -3876,18 +3847,9 @@ public class MessagingController implements Runnable { return (account.getRemoteStore() instanceof Pop3Store); } public void sendAlternate(final Context context, Account account, Message message) { if (K9.DEBUG) Log.d(K9.LOG_TAG, "About to load message " + account.getDescription() + ":" + message.getFolder().getName() + ":" + message.getUid() + " for sendAlternate"); loadMessageForView(account, message.getFolder().getName(), message.getUid(), new MessagingListener() { @Override public void loadMessageForViewBodyAvailable(Account account, String folder, String uid, LocalMessage message) { public void sendAlternate(Context context, Account account, LocalMessage message) { if (K9.DEBUG) Log.d(K9.LOG_TAG, "Got message " + account.getDescription() + ":" + folder Log.d(K9.LOG_TAG, "Got message " + account.getDescription() + ":" + message.getFolder() + ":" + message.getUid() + " for sendAlternate"); try { Loading Loading @@ -3932,9 +3894,6 @@ public class MessagingController implements Runnable { Log.e(K9.LOG_TAG, "Unable to send email through alternate program", me); } } }); } /** * Checks mail for one or multiple accounts. If account is null all accounts Loading
k9mail/src/main/java/com/fsck/k9/controller/MessagingListener.java +2 −18 Original line number Diff line number Diff line Loading @@ -81,27 +81,11 @@ public class MessagingListener { public void synchronizeMailboxFailed(Account account, String folder, String message) {} public void loadMessageRemoteFinished(Account account, String folder, String uid) {} public void loadMessageForViewStarted(Account account, String folder, String uid) {} public void loadMessageForViewHeadersAvailable(Account account, String folder, String uid, Message message) {} public void loadMessageForViewBodyAvailable(Account account, String folder, String uid, LocalMessage message) {} public void loadMessageForViewFinished(Account account, String folder, String uid, LocalMessage message) {} public void loadMessageForViewFailed(Account account, String folder, String uid, public void loadMessageRemoteFailed(Account account, String folder, String uid, Throwable t) {} /** * Called when a message for view has been fully displayed on the screen. */ public void messageViewFinished() {} public void checkMailStarted(Context context, Account account) {} public void checkMailFinished(Context context, Account account) {} Loading
k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java +4 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes