k-9/src/com/fsck/k9/activity/MessageView.java

1162 lines
41 KiB
Java
Raw Normal View History

package com.fsck.k9.activity;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.actionbarsherlock.view.Window;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.helper.FileBrowserHelper;
import com.fsck.k9.helper.FileBrowserHelper.FileBrowserFailOverCallback;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.view.AttachmentView;
import com.fsck.k9.view.AttachmentView.AttachmentFileDownloadCallback;
import com.fsck.k9.view.MessageTitleView;
import com.fsck.k9.view.SingleMessageView;
public class MessageView extends K9Activity implements OnClickListener {
private static final String EXTRA_MESSAGE_REFERENCE = "com.fsck.k9.MessageView_messageReference";
private static final String EXTRA_MESSAGE_REFERENCES = "com.fsck.k9.MessageView_messageReferences";
private static final String EXTRA_MESSAGE_LIST_EXTRAS = "com.fsck.k9.MessageView_messageListExtras";
private static final String STATE_PGP_DATA = "pgpData";
private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1;
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
private static final int ACTIVITY_CHOOSE_DIRECTORY = 3;
private SingleMessageView mMessageView;
private MessageTitleView mTitleView;
private PgpData mPgpData;
private Menu mMenu;
private Account mAccount;
private MessageReference mMessageReference;
private ArrayList<MessageReference> mMessageReferences;
private Message mMessage;
private static final int PREVIOUS = 1;
private static final int NEXT = 2;
private int mLastDirection = (K9.messageViewShowNext()) ? NEXT : PREVIOUS;
private MessagingController mController = MessagingController.getInstance(getApplication());
private MessageReference mNextMessage = null;
private MessageReference mPreviousMessage = null;
private Listener mListener = new Listener();
private MessageViewHandler mHandler = new MessageViewHandler();
Merge branch 'mail-on-sd' * mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
2010-11-13 16:40:56 -05:00
private StorageManager.StorageListener mStorageListener = new StorageListenerImplementation();
private MenuItem mToggleMessageViewMenu;
/** this variable is used to save the calling AttachmentView
* until the onActivityResult is called.
* => with this reference we can identity the caller
*/
private AttachmentView attachmentTmpStore;
/**
* Used to temporarily store the destination folder for refile operations if a confirmation
* dialog is shown.
*/
private String mDstFolder;
/**
* The extras used to create the {@link MessageList} instance that created this activity. May
* be {@code null}.
*
* @see MessageList#actionHandleFolder(Context, Bundle)
*/
private Bundle mMessageListExtras;
/**
* Screen width in pixels.
*
* <p>
* Used to detect right-to-left bezel swipes.
* </p>
*
* @see #onSwipeRightToLeft(MotionEvent, MotionEvent)
*/
private int mScreenWidthInPixels;
private final class StorageListenerImplementation implements StorageManager.StorageListener {
Merge branch 'mail-on-sd' * mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
2010-11-13 16:40:56 -05:00
@Override
public void onUnmount(String providerId) {
if (!providerId.equals(mAccount.getLocalStorageProviderId())) {
2011-01-06 11:55:34 -05:00
return;
Merge branch 'mail-on-sd' * mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
2010-11-13 16:40:56 -05:00
}
runOnUiThread(new Runnable() {
2011-01-06 11:55:34 -05:00
@Override
public void run() {
2011-01-06 11:55:34 -05:00
onAccountUnavailable();
}
});
Merge branch 'mail-on-sd' * mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
2010-11-13 16:40:56 -05:00
}
@Override
2011-08-30 20:17:45 -04:00
public void onMount(String providerId) { /* no-op */ }
Merge branch 'mail-on-sd' * mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
2010-11-13 16:40:56 -05:00
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
boolean ret = false;
if (KeyEvent.ACTION_DOWN == event.getAction()) {
ret = onCustomKeyDown(event.getKeyCode(), event);
}
if (!ret) {
ret = super.dispatchKeyEvent(event);
}
return ret;
}
/**
* Handle hotkeys
*
* <p>
* This method is called by {@link #dispatchKeyEvent(KeyEvent)} before any view had the chance
* to consume this key event.
* </p>
*
* @param keyCode
* The value in {@code event.getKeyCode()}.
* @param event
* Description of the key event.
*
* @return {@code true} if this event was consumed.
*/
public boolean onCustomKeyDown(final int keyCode, final KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP: {
if (K9.useVolumeKeysForNavigationEnabled()) {
onNext();
return true;
}
break;
}
case KeyEvent.KEYCODE_VOLUME_DOWN: {
if (K9.useVolumeKeysForNavigationEnabled()) {
onPrevious();
return true;
}
break;
}
case KeyEvent.KEYCODE_DEL: {
onDelete();
return true;
}
case KeyEvent.KEYCODE_D: {
onDelete();
return true;
}
case KeyEvent.KEYCODE_F: {
onForward();
return true;
}
case KeyEvent.KEYCODE_A: {
onReplyAll();
return true;
}
case KeyEvent.KEYCODE_R: {
onReply();
return true;
}
case KeyEvent.KEYCODE_G: {
onFlag();
return true;
}
case KeyEvent.KEYCODE_M: {
onMove();
return true;
}
case KeyEvent.KEYCODE_S: {
onRefile(mAccount.getSpamFolderName());
return true;
}
case KeyEvent.KEYCODE_V: {
onRefile(mAccount.getArchiveFolderName());
return true;
}
case KeyEvent.KEYCODE_Y: {
onCopy();
return true;
}
case KeyEvent.KEYCODE_J:
case KeyEvent.KEYCODE_P: {
onPrevious();
return true;
}
case KeyEvent.KEYCODE_N:
case KeyEvent.KEYCODE_K: {
onNext();
return true;
}
case KeyEvent.KEYCODE_Z: {
mHandler.post(new Runnable() {
public void run() {
mMessageView.zoom(event);
}
});
return true;
}
case KeyEvent.KEYCODE_H: {
Toast toast = Toast.makeText(this, R.string.message_help_key, Toast.LENGTH_LONG);
toast.show();
return true;
}
}
return false;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// Swallow these events too to avoid the audible notification of a volume change
if (K9.useVolumeKeysForNavigationEnabled()) {
if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) {
if (K9.DEBUG)
Log.v(K9.LOG_TAG, "Swallowed key up.");
return true;
}
}
2010-12-25 22:49:23 -05:00
return super.onKeyUp(keyCode, event);
}
class MessageViewHandler extends Handler {
public void progress(final boolean progress) {
runOnUiThread(new Runnable() {
public void run() {
2012-08-24 12:39:23 -04:00
setSupportProgressBarIndeterminateVisibility(progress);
}
});
}
public void addAttachment(final View attachmentView) {
runOnUiThread(new Runnable() {
public void run() {
mMessageView.addAttachment(attachmentView);
}
});
}
2011-02-14 20:26:00 -05:00
/* A helper for a set of "show a toast" methods */
private void showToast(final String message, final int toastLength) {
runOnUiThread(new Runnable() {
public void run() {
2011-02-14 20:26:00 -05:00
Toast.makeText(MessageView.this, message, toastLength).show();
}
});
}
2011-02-14 20:26:00 -05:00
public void networkError() {
showToast(getString(R.string.status_network_error), Toast.LENGTH_LONG);
}
public void invalidIdError() {
2011-02-14 20:26:00 -05:00
showToast(getString(R.string.status_invalid_id_error), Toast.LENGTH_LONG);
}
public void fetchingAttachment() {
2011-02-14 20:26:00 -05:00
showToast(getString(R.string.message_view_fetching_attachment_toast), Toast.LENGTH_SHORT);
}
}
2011-08-30 20:17:45 -04:00
public static void actionView(Context context, MessageReference messRef,
ArrayList<MessageReference> messReferences, Bundle messageListExtras) {
Intent i = new Intent(context, MessageView.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra(EXTRA_MESSAGE_LIST_EXTRAS, messageListExtras);
i.putExtra(EXTRA_MESSAGE_REFERENCE, messRef);
i.putParcelableArrayListExtra(EXTRA_MESSAGE_REFERENCES, messReferences);
context.startActivity(i);
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.message_view);
2009-11-19 01:03:59 -05:00
mMessageView = (SingleMessageView) findViewById(R.id.message_view);
//set a callback for the attachment view. With this callback the attachmentview
//request the start of a filebrowser activity.
mMessageView.setAttachmentCallback(new AttachmentFileDownloadCallback() {
@Override
public void showFileBrowser(final AttachmentView caller) {
FileBrowserHelper.getInstance()
.showFileBrowserActivity(MessageView.this,
null,
MessageView.ACTIVITY_CHOOSE_DIRECTORY,
callback);
attachmentTmpStore = caller;
}
FileBrowserFailOverCallback callback = new FileBrowserFailOverCallback() {
@Override
public void onPathEntered(String path) {
attachmentTmpStore.writeFile(new File(path));
}
@Override
public void onCancel() {
// canceled, do nothing
}
};
});
mMessageView.initialize(this);
2012-09-09 19:04:47 -04:00
mMessageView.downloadRemainderButton().setOnClickListener(this);
initializeActionBar();
setTitle("");
final Intent intent = getIntent();
mMessageListExtras = intent.getParcelableExtra(EXTRA_MESSAGE_LIST_EXTRAS);
Uri uri = intent.getData();
if (icicle != null) {
// TODO This code seems unnecessary since the icicle should already be thawed in onRestoreInstanceState().
mMessageReference = icicle.getParcelable(EXTRA_MESSAGE_REFERENCE);
mMessageReferences = icicle.getParcelableArrayList(EXTRA_MESSAGE_REFERENCES);
mPgpData = (PgpData) icicle.getSerializable(STATE_PGP_DATA);
} else {
if (uri == null) {
mMessageReference = intent.getParcelableExtra(EXTRA_MESSAGE_REFERENCE);
mMessageReferences = intent.getParcelableArrayListExtra(EXTRA_MESSAGE_REFERENCES);
} else {
List<String> segmentList = uri.getPathSegments();
if (segmentList.size() != 3) {
2011-08-30 20:17:45 -04:00
//TODO: Use resource to externalize message
Toast.makeText(this, "Invalid intent uri: " + uri.toString(), Toast.LENGTH_LONG).show();
return;
}
String accountId = segmentList.get(0);
Collection<Account> accounts = Preferences.getPreferences(this).getAvailableAccounts();
boolean found = false;
for (Account account : accounts) {
if (String.valueOf(account.getAccountNumber()).equals(accountId)) {
mAccount = account;
found = true;
break;
2010-07-27 08:10:09 -04:00
}
}
if (!found) {
2011-08-30 20:17:45 -04:00
//TODO: Use resource to externalize message
Toast.makeText(this, "Invalid account id: " + accountId, Toast.LENGTH_LONG).show();
return;
2010-07-27 08:10:09 -04:00
}
mMessageReference = new MessageReference();
mMessageReference.accountUuid = mAccount.getUuid();
mMessageReference.folderName = segmentList.get(1);
mMessageReference.uid = segmentList.get(2);
mMessageReferences = new ArrayList<MessageReference>();
2010-07-27 08:10:09 -04:00
}
}
2010-07-27 08:10:09 -04:00
mAccount = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid);
mScreenWidthInPixels = getResources().getDisplayMetrics().widthPixels;
Fix gesture detection This commit addresses 2 issues: 1) Before, a general GestureDetector was registered on the highest level in K9Activity This resulted in EVERY inherited activity to have a useless, unused gesture detector. But more than that, in MessageList, a second GestureDetector was assigned to the ListView. On every fling gesture, both detectors called the onSwipe() methods, which technically did the following: - The one directly assigned to the ListView would work corectly by mapping the (local) event coordinates to the right entry in the ListView - The global one worked on screen coordinates, so the onSwipe() method would likely select the wrong ListView entry (system menu bar offset). - For some reason this "worked" fine, and only the correct entry was selected, despite two detectors used. 2) The gesture detection for the MessageView caused problems when the message itself was scrollable, i.e. wide HTML mails. A fling gesture inside the WebView would scroll the message, but also switch the message. This commit fixes all those by doing the following: - Don't register the GestureDetector in K9Activity, instead make the member variable accessible by subclasses. - In the subclasses that need a detector register it - In K9Activity.dispatchTouchEvent() check for mGestureDetector being null - For MessageList: * Remove the duplicate gesture detector assigned to the ListView * in the handleSwipe() methods: calclulate pixel offset of the ListView to make it work using the global screen coordinates - For MessageView: Limit sensitive area to the message header, to prevent interference with the WebView scrolling - Respect current behavior: * Force-enable gestures for the MessageList * Respect user setting in MessageView - Make sure that after a successful swipe gesture, any pending action is cancelled, to prevent unwanted things to happen (such as expanding the header after changing the message, or a context menu popping up in the MessageList). See http://code.google.com/p/android/issues/detail?id=8497
2012-04-30 19:56:06 -04:00
// Enable gesture detection for MessageViews
mGestureDetector = new GestureDetector(new MyGestureDetector(false));
displayMessage(mMessageReference);
}
2009-10-22 11:54:49 -04:00
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(EXTRA_MESSAGE_REFERENCE, mMessageReference);
outState.putParcelableArrayList(EXTRA_MESSAGE_REFERENCES, mMessageReferences);
outState.putSerializable(STATE_PGP_DATA, mPgpData);
2010-07-27 08:10:09 -04:00
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
2010-07-27 08:10:09 -04:00
super.onRestoreInstanceState(savedInstanceState);
mPgpData = (PgpData) savedInstanceState.getSerializable(STATE_PGP_DATA);
mMessageView.updateCryptoLayout(mAccount.getCryptoProvider(), mPgpData, mMessage);
2009-10-22 11:54:49 -04:00
}
private void initializeActionBar() {
final ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setCustomView(R.layout.actionbar_message_view);
final View customView = actionBar.getCustomView();
mTitleView = (MessageTitleView) customView.findViewById(android.R.id.title);
mTitleView.setMessageHeader(mMessageView.getMessageHeaderView());
}
private void displayMessage(MessageReference ref) {
mMessageReference = ref;
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "MessageView displaying message " + mMessageReference);
mAccount = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid);
findSurroundingMessagesUid();
// start with fresh, empty PGP data
mPgpData = new PgpData();
// Clear previous message
mMessageView.resetView();
mMessageView.resetHeaderView();
mController.loadMessageForView(mAccount, mMessageReference.folderName, mMessageReference.uid, mListener);
configureMenu(mMenu);
Complete merge of DAmail functionality into K9mail. Following features are added to K9mail: 1) Show unread message count on each folder 2) Sum unread count of all shown folders in an account to the account display 3) Periodically check selected folders for new mail, not just Inbox 4) Don't refresh folder when opened (unless folder is empty) 5) Show date and time of last sync for each folder 6) Fix timer for automatic periodic sync (use wakelock to assure completion) 7) Optimize local folder queries (speeds up account and folder lists) 8) Show Loading... message in status bar indicating which folder is being synced 9) Eliminate redundant sync of new messages (performance enhancement) 10) Improve notification text for multiple accounts 11) Do not automatically sync folders more often than the account-specific period 12) Use user-configured date and time formats 13) Select which folders are shown, using configurable Classes 14) Select which folders are synced, using configurable Classes 15) Added context (long press) menu to folders, to provide for Refresh and Folder Settings 16) Status light flashes purple when there are unread messages 17) Folder list more quickly eliminates display of deleted and out-of-Class folders. 18) Delete works 19) Mark all messages as read (in the folder context menu) 20) Notifications only for new unread messages 21) One minute synchronization frequency 22) Deleting an unread message decrements unread counter 23) Notifications work for POP3 accounts 24) Message deletes work for POP3 accounts 25) Explicit errors show in folder list 26) Stack traces saved to folder K9mail-errors 27) Clear pending actions (danger, for emergencies only!) 28) Delete policy in Account settings 29) DNS cache in InetAddress disabled 30) Trapped some crash-causing error conditions 31) Eliminate duplicate copies to Sent folder 32) Prevent crashes due to message listener concurrency 33) Empty Trash 34) Nuclear "Mark all messages as read" (marks all messages as read in server-side folder, irrespective of which messages have been downloaded) 35) Forward (alternate) to allow forwarding email through other programs 36) Accept text/plain Intents to allow other programs to send email through K9mail 37) Displays Outbox sending status 38) Manual retry of outbox sending when "Refresh"ing Outbox 39) Folder error status is persisted 40) Ability to log to arbitrary file Fixes K9 issues 11, 23, 24, 65, 69, 71, 79, 81, 82, 83, 87, 101, 104, 107, 120, 148, 154
2008-12-30 22:49:09 -05:00
}
private void findSurroundingMessagesUid() {
mNextMessage = mPreviousMessage = null;
int i = mMessageReferences.indexOf(mMessageReference);
if (i < 0)
return;
if (i != 0)
mNextMessage = mMessageReferences.get(i - 1);
if (i != (mMessageReferences.size() - 1))
mPreviousMessage = mMessageReferences.get(i + 1);
}
@Override
public void onResume() {
super.onResume();
if (!mAccount.isAvailable(this)) {
Merge branch 'mail-on-sd' * mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
2010-11-13 16:40:56 -05:00
onAccountUnavailable();
return;
}
StorageManager.getInstance(getApplication()).addListener(mStorageListener);
}
@Override
protected void onPause() {
Merge branch 'mail-on-sd' * mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
2010-11-13 16:40:56 -05:00
StorageManager.getInstance(getApplication()).removeListener(mStorageListener);
super.onPause();
}
protected void onAccountUnavailable() {
Merge branch 'mail-on-sd' * mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
2010-11-13 16:40:56 -05:00
finish();
// TODO inform user about account unavailability using Toast
Accounts.listAccounts(this);
}
2009-10-22 11:54:49 -04:00
/**
* Called from UI thread when user select Delete
*/
private void onDelete() {
if (K9.confirmDelete() || (K9.confirmDeleteStarred() && mMessage.isSet(Flag.FLAGGED))) {
showDialog(R.id.dialog_confirm_delete);
} else {
delete();
}
}
private void delete() {
if (mMessage != null) {
// Disable the delete button after it's tapped (to try to prevent
// accidental clicks)
mMenu.findItem(R.id.delete).setEnabled(false);
Message messageToDelete = mMessage;
showNextMessageOrReturn();
2011-02-14 20:45:08 -05:00
mController.deleteMessages(new Message[] {messageToDelete}, null);
}
}
private void onRefile(String dstFolder) {
if (!mController.isMoveCapable(mAccount)) {
return;
}
if (!mController.isMoveCapable(mMessage)) {
Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
toast.show();
return;
}
if (K9.FOLDER_NONE.equalsIgnoreCase(dstFolder)) {
return;
}
if (mAccount.getSpamFolderName().equals(dstFolder) && K9.confirmSpam()) {
mDstFolder = dstFolder;
showDialog(R.id.dialog_confirm_spam);
} else {
refileMessage(dstFolder);
}
}
private void refileMessage(String dstFolder) {
String srcFolder = mMessageReference.folderName;
Message messageToMove = mMessage;
showNextMessageOrReturn();
2011-02-14 20:45:08 -05:00
mController.moveMessage(mAccount, srcFolder, messageToMove, dstFolder, null);
}
private void showNextMessageOrReturn() {
if (K9.messageViewReturnToList()) {
finish();
} else {
showNextMessage();
}
}
private void showNextMessage() {
findSurroundingMessagesUid();
mMessageReferences.remove(mMessageReference);
if (mLastDirection == NEXT && mNextMessage != null) {
onNext();
} else if (mLastDirection == PREVIOUS && mPreviousMessage != null) {
onPrevious();
} else if (mNextMessage != null) {
onNext();
} else if (mPreviousMessage != null) {
onPrevious();
} else {
finish();
}
}
private void onReply() {
if (mMessage != null) {
MessageCompose.actionReply(this, mAccount, mMessage, false, mPgpData.getDecryptedData());
finish();
}
}
private void onReplyAll() {
if (mMessage != null) {
MessageCompose.actionReply(this, mAccount, mMessage, true, mPgpData.getDecryptedData());
finish();
}
}
private void onForward() {
if (mMessage != null) {
MessageCompose.actionForward(this, mAccount, mMessage, mPgpData.getDecryptedData());
finish();
}
}
private void onFlag() {
if (mMessage != null) {
boolean newState = !mMessage.isSet(Flag.FLAGGED);
mController.setFlag(mAccount, mMessage.getFolder().getName(),
new Message[] { mMessage }, Flag.FLAGGED, newState);
mMessageView.setHeaders(mMessage, mAccount);
}
}
private void onMove() {
if ((!mController.isMoveCapable(mAccount))
|| (mMessage == null)) {
return;
}
if (!mController.isMoveCapable(mMessage)) {
Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
toast.show();
return;
}
2010-12-25 22:49:13 -05:00
startRefileActivity(ACTIVITY_CHOOSE_FOLDER_MOVE);
}
private void onCopy() {
if ((!mController.isCopyCapable(mAccount))
|| (mMessage == null)) {
return;
}
if (!mController.isCopyCapable(mMessage)) {
Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
toast.show();
return;
}
2010-12-25 22:49:13 -05:00
startRefileActivity(ACTIVITY_CHOOSE_FOLDER_COPY);
}
private void onToggleColors() {
if (K9.getK9MessageViewTheme() == K9.THEME_DARK) {
K9.setK9MessageViewTheme(K9.THEME_LIGHT);
} else {
K9.setK9MessageViewTheme(K9.THEME_DARK);
}
new AsyncTask<Object, Object, Object>() {
@Override
protected Object doInBackground(Object... params) {
Preferences prefs = Preferences.getPreferences(getApplicationContext());
Editor editor = prefs.getPreferences().edit();
K9.save(editor);
editor.commit();
return null;
}
}.execute();
displayMessage(mMessageReference);
}
private void startRefileActivity(int activity) {
Intent intent = new Intent(this, ChooseFolder.class);
intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, mAccount.getUuid());
intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, mMessageReference.folderName);
intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, mAccount.getLastSelectedFolderName());
intent.putExtra(ChooseFolder.EXTRA_MESSAGE, mMessageReference);
2010-12-25 22:49:13 -05:00
startActivityForResult(intent, activity);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mAccount.getCryptoProvider().onActivityResult(this, requestCode, resultCode, data, mPgpData)) {
2010-07-27 08:10:09 -04:00
return;
}
if (resultCode != RESULT_OK)
return;
switch (requestCode) {
case ACTIVITY_CHOOSE_DIRECTORY:
if (resultCode == RESULT_OK && data != null) {
// obtain the filename
Uri fileUri = data.getData();
if (fileUri != null) {
String filePath = fileUri.getPath();
if (filePath != null) {
attachmentTmpStore.writeFile(new File(filePath));
}
}
}
break;
case ACTIVITY_CHOOSE_FOLDER_MOVE:
case ACTIVITY_CHOOSE_FOLDER_COPY:
if (data == null)
return;
String destFolderName = data.getStringExtra(ChooseFolder.EXTRA_NEW_FOLDER);
String srcFolderName = data.getStringExtra(ChooseFolder.EXTRA_CUR_FOLDER);
MessageReference ref = data.getParcelableExtra(ChooseFolder.EXTRA_MESSAGE);
if (mMessageReference.equals(ref)) {
mAccount.setLastSelectedFolderName(destFolderName);
switch (requestCode) {
case ACTIVITY_CHOOSE_FOLDER_MOVE:
Message messageToMove = mMessage;
showNextMessageOrReturn();
2011-02-14 20:45:08 -05:00
mController.moveMessage(mAccount, srcFolderName, messageToMove, destFolderName, null);
break;
case ACTIVITY_CHOOSE_FOLDER_COPY:
2011-02-14 20:45:08 -05:00
mController.copyMessage(mAccount, srcFolderName, mMessage, destFolderName, null);
break;
}
}
break;
}
}
private void onSendAlternate() {
if (mMessage != null) {
mController.sendAlternate(this, mAccount, mMessage);
}
}
/**
* Handle a right-to-left swipe starting at the edge of the screen as "move to next message."
*/
@Override
protected void onSwipeRightToLeft(MotionEvent e1, MotionEvent e2) {
if ((int) e1.getRawX() > mScreenWidthInPixels - BEZEL_SWIPE_THRESHOLD) {
Fix gesture detection This commit addresses 2 issues: 1) Before, a general GestureDetector was registered on the highest level in K9Activity This resulted in EVERY inherited activity to have a useless, unused gesture detector. But more than that, in MessageList, a second GestureDetector was assigned to the ListView. On every fling gesture, both detectors called the onSwipe() methods, which technically did the following: - The one directly assigned to the ListView would work corectly by mapping the (local) event coordinates to the right entry in the ListView - The global one worked on screen coordinates, so the onSwipe() method would likely select the wrong ListView entry (system menu bar offset). - For some reason this "worked" fine, and only the correct entry was selected, despite two detectors used. 2) The gesture detection for the MessageView caused problems when the message itself was scrollable, i.e. wide HTML mails. A fling gesture inside the WebView would scroll the message, but also switch the message. This commit fixes all those by doing the following: - Don't register the GestureDetector in K9Activity, instead make the member variable accessible by subclasses. - In the subclasses that need a detector register it - In K9Activity.dispatchTouchEvent() check for mGestureDetector being null - For MessageList: * Remove the duplicate gesture detector assigned to the ListView * in the handleSwipe() methods: calclulate pixel offset of the ListView to make it work using the global screen coordinates - For MessageView: Limit sensitive area to the message header, to prevent interference with the WebView scrolling - Respect current behavior: * Force-enable gestures for the MessageList * Respect user setting in MessageView - Make sure that after a successful swipe gesture, any pending action is cancelled, to prevent unwanted things to happen (such as expanding the header after changing the message, or a context menu popping up in the MessageList). See http://code.google.com/p/android/issues/detail?id=8497
2012-04-30 19:56:06 -04:00
onNext();
}
}
/**
* Handle a left-to-right swipe starting at the edge of the screen as
* "move to previous message."
*/
@Override
protected void onSwipeLeftToRight(MotionEvent e1, MotionEvent e2) {
if ((int) e1.getRawX() < BEZEL_SWIPE_THRESHOLD) {
Fix gesture detection This commit addresses 2 issues: 1) Before, a general GestureDetector was registered on the highest level in K9Activity This resulted in EVERY inherited activity to have a useless, unused gesture detector. But more than that, in MessageList, a second GestureDetector was assigned to the ListView. On every fling gesture, both detectors called the onSwipe() methods, which technically did the following: - The one directly assigned to the ListView would work corectly by mapping the (local) event coordinates to the right entry in the ListView - The global one worked on screen coordinates, so the onSwipe() method would likely select the wrong ListView entry (system menu bar offset). - For some reason this "worked" fine, and only the correct entry was selected, despite two detectors used. 2) The gesture detection for the MessageView caused problems when the message itself was scrollable, i.e. wide HTML mails. A fling gesture inside the WebView would scroll the message, but also switch the message. This commit fixes all those by doing the following: - Don't register the GestureDetector in K9Activity, instead make the member variable accessible by subclasses. - In the subclasses that need a detector register it - In K9Activity.dispatchTouchEvent() check for mGestureDetector being null - For MessageList: * Remove the duplicate gesture detector assigned to the ListView * in the handleSwipe() methods: calclulate pixel offset of the ListView to make it work using the global screen coordinates - For MessageView: Limit sensitive area to the message header, to prevent interference with the WebView scrolling - Respect current behavior: * Force-enable gestures for the MessageList * Respect user setting in MessageView - Make sure that after a successful swipe gesture, any pending action is cancelled, to prevent unwanted things to happen (such as expanding the header after changing the message, or a context menu popping up in the MessageList). See http://code.google.com/p/android/issues/detail?id=8497
2012-04-30 19:56:06 -04:00
onPrevious();
}
}
protected void onNext() {
// Reset scroll percentage when we change messages
if (mNextMessage == null) {
Toast.makeText(this, getString(R.string.end_of_folder), Toast.LENGTH_SHORT).show();
return;
}
mLastDirection = NEXT;
//toggleActionsState(mMenu, false);
if (K9.showAnimations()) {
mMessageView.startAnimation(outToLeftAnimation());
}
displayMessage(mNextMessage);
}
protected void onPrevious() {
// Reset scroll percentage when we change messages
if (mPreviousMessage == null) {
Toast.makeText(this, getString(R.string.end_of_folder), Toast.LENGTH_SHORT).show();
return;
}
mLastDirection = PREVIOUS;
//toggleActionsState(mMenu, false);
if (K9.showAnimations()) {
mMessageView.startAnimation(inFromRightAnimation());
}
displayMessage(mPreviousMessage);
}
private void onToggleRead() {
if (mMessage != null) {
mController.setFlag(mAccount, mMessage.getFolder().getName(),
new Message[] { mMessage }, Flag.SEEN, !mMessage.isSet(Flag.SEEN));
mMessageView.setHeaders(mMessage, mAccount);
String subject = mMessage.getSubject();
setTitle(subject);
updateUnreadToggleTitle();
}
}
private void onDownloadRemainder() {
if (mMessage.isSet(Flag.X_DOWNLOADED_FULL)) {
2010-07-18 21:57:49 -04:00
return;
}
mMessageView.downloadRemainderButton().setEnabled(false);
mController.loadMessageForViewRemote(mAccount, mMessageReference.folderName, mMessageReference.uid, mListener);
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.download:
((AttachmentView)view).saveFile();
break;
case R.id.download_remainder:
onDownloadRemainder();
break;
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
break;
case R.id.next_message:
2012-09-09 17:25:52 -04:00
onNext();
break;
case R.id.previous_message:
2012-09-09 17:25:52 -04:00
onPrevious();
break;
case R.id.delete:
onDelete();
break;
case R.id.reply:
onReply();
break;
case R.id.reply_all:
onReplyAll();
break;
case R.id.forward:
onForward();
break;
case R.id.share:
onSendAlternate();
break;
case R.id.toggle_unread:
onToggleRead();
break;
case R.id.archive:
onRefile(mAccount.getArchiveFolderName());
break;
case R.id.spam:
onRefile(mAccount.getSpamFolderName());
break;
case R.id.move:
onMove();
break;
case R.id.copy:
onCopy();
break;
case R.id.select_text:
mMessageView.beginSelectingText();
break;
case R.id.toggle_message_view_theme:
onToggleColors();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
2009-11-16 14:27:57 -05:00
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getSupportMenuInflater().inflate(R.menu.message_view_option, menu);
mMenu = menu;
configureMenu(menu);
return true;
}
2012-09-09 17:25:52 -04:00
private void configureMenu(Menu menu) {
// first run displayMessage() gets called before onCreateOptionMenu()
if (menu == null) {
return;
}
2012-09-09 17:25:52 -04:00
// enable them all
menu.findItem(R.id.copy).setVisible(true);
menu.findItem(R.id.move).setVisible(true);
menu.findItem(R.id.archive).setVisible(true);
menu.findItem(R.id.spam).setVisible(true);
mToggleMessageViewMenu = menu.findItem(R.id.toggle_message_view_theme);
if (K9.getK9MessageViewTheme() == K9.THEME_DARK) {
mToggleMessageViewMenu.setTitle(R.string.message_view_theme_action_light);
} else {
mToggleMessageViewMenu.setTitle(R.string.message_view_theme_action_dark);
}
toggleActionsState(menu, true);
2012-09-09 17:25:52 -04:00
if (mNextMessage != null) {
2012-09-09 17:25:52 -04:00
menu.findItem(R.id.next_message).setEnabled(true);
menu.findItem(R.id.next_message).getIcon().setAlpha(255);
} else {
2012-09-09 17:25:52 -04:00
menu.findItem(R.id.next_message).getIcon().setAlpha(127);
menu.findItem(R.id.next_message).setEnabled(false);
}
2012-09-09 17:25:52 -04:00
if (mPreviousMessage != null) {
menu.findItem(R.id.previous_message).setEnabled(true);
menu.findItem(R.id.previous_message).getIcon().setAlpha(255);
} else {
2012-09-09 17:25:52 -04:00
menu.findItem(R.id.previous_message).getIcon().setAlpha(127);
menu.findItem(R.id.previous_message).setEnabled(false);
}
updateUnreadToggleTitle();
// comply with the setting
if (!mAccount.getEnableMoveButtons()) {
menu.findItem(R.id.move).setVisible(false);
menu.findItem(R.id.archive).setVisible(false);
menu.findItem(R.id.spam).setVisible(false);
} else {
2012-09-09 17:25:52 -04:00
// check message, folder capability
if (!mController.isCopyCapable(mAccount)) {
menu.findItem(R.id.copy).setVisible(false);
}
if (mController.isMoveCapable(mAccount)) {
menu.findItem(R.id.move).setVisible(true);
menu.findItem(R.id.archive).setVisible(
2012-09-09 17:25:52 -04:00
!mMessageReference.folderName.equals(mAccount.getArchiveFolderName())
&& mAccount.hasArchiveFolder());
menu.findItem(R.id.spam).setVisible(
2012-09-09 17:25:52 -04:00
!mMessageReference.folderName.equals(mAccount.getSpamFolderName())
&& mAccount.hasSpamFolder());
} else {
2012-09-09 17:25:52 -04:00
menu.findItem(R.id.copy).setVisible(false);
menu.findItem(R.id.move).setVisible(false);
menu.findItem(R.id.archive).setVisible(false);
menu.findItem(R.id.spam).setVisible(false);
}
}
}
/**
* Set the title of the "Toggle Unread" menu item based upon the current read state of the message.
*/
public void updateUnreadToggleTitle() {
if (mMessage != null && mMenu != null) {
if (mMessage.isSet(Flag.SEEN)) {
mMenu.findItem(R.id.toggle_unread).setTitle(R.string.mark_as_unread_action);
} else {
mMenu.findItem(R.id.toggle_unread).setTitle(R.string.mark_as_read_action);
}
}
}
2012-09-09 17:25:52 -04:00
private void toggleActionsState(Menu menu, boolean state) {
for (int i = 0; i < menu.size(); ++i) {
menu.getItem(i).setEnabled(state);
}
}
2009-11-16 14:27:57 -05:00
2010-12-24 19:27:09 -05:00
// TODO: when switching to API version 8, override onCreateDialog(int, Bundle)
2010-12-25 22:49:23 -05:00
/**
2010-12-25 22:49:23 -05:00
* @param id The id of the dialog.
* @return The dialog. If you return null, the dialog will not be created.
2010-12-24 19:27:09 -05:00
* @see android.app.Activity#onCreateDialog(int)
*/
@Override
protected Dialog onCreateDialog(final int id) {
switch (id) {
2011-03-22 03:07:32 -04:00
case R.id.dialog_confirm_delete:
return ConfirmationDialog.create(this, id,
2011-04-12 08:16:22 -04:00
R.string.dialog_confirm_delete_title,
R.string.dialog_confirm_delete_message,
R.string.dialog_confirm_delete_confirm_button,
R.string.dialog_confirm_delete_cancel_button,
new Runnable() {
@Override
public void run() {
delete();
}
});
case R.id.dialog_confirm_spam:
return ConfirmationDialog.create(this, id,
2011-04-12 08:16:22 -04:00
R.string.dialog_confirm_spam_title,
getResources().getQuantityString(R.plurals.dialog_confirm_spam_message, 1),
2011-04-12 08:16:22 -04:00
R.string.dialog_confirm_spam_confirm_button,
R.string.dialog_confirm_spam_cancel_button,
new Runnable() {
@Override
public void run() {
refileMessage(mDstFolder);
mDstFolder = null;
}
});
2011-03-22 03:07:32 -04:00
case R.id.dialog_attachment_progress:
ProgressDialog d = new ProgressDialog(this);
d.setIndeterminate(true);
d.setTitle(R.string.dialog_attachment_progress_title);
return d;
}
return super.onCreateDialog(id);
}
class Listener extends MessagingListener {
@Override
public void loadMessageForViewHeadersAvailable(final Account account, String folder, String uid,
final Message message) {
if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder)
|| !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();
runOnUiThread(new Runnable() {
public void run() {
if (!clonedMessage.isSet(Flag.X_DOWNLOADED_FULL) &&
!clonedMessage.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
2012-04-04 04:04:57 -04:00
String text = getString(R.string.message_view_downloading);
mMessageView.showStatusMessage(text);
}
mMessageView.setHeaders(clonedMessage, account);
final String subject = clonedMessage.getSubject();
if (subject == null || subject.equals("")) {
setTitle(getString(R.string.general_no_subject));
} else {
setTitle(clonedMessage.getSubject());
}
2011-02-11 12:10:45 -05:00
mMessageView.setOnFlagListener(new OnClickListener() {
@Override
public void onClick(View v) {
onFlag();
}
});
}
});
}
@Override
public void loadMessageForViewBodyAvailable(final Account account, String folder,
String uid, final Message message) {
if (!mMessageReference.uid.equals(uid) ||
!mMessageReference.folderName.equals(folder) ||
!mMessageReference.accountUuid.equals(account.getUuid())) {
return;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
mMessage = message;
mMessageView.setMessage(account, (LocalMessage) message, mPgpData,
mController, mListener);
updateUnreadToggleTitle();
} catch (MessagingException e) {
Log.v(K9.LOG_TAG, "loadMessageForViewBodyAvailable", e);
}
}
});
}
@Override
2011-02-14 20:45:08 -05:00
public void loadMessageForViewFailed(Account account, String folder, String uid, final Throwable t) {
if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder)
|| !mMessageReference.accountUuid.equals(account.getUuid())) {
return;
}
mHandler.post(new Runnable() {
public void run() {
2012-08-24 12:39:23 -04:00
setSupportProgressBarIndeterminateVisibility(false);
if (t instanceof IllegalArgumentException) {
mHandler.invalidIdError();
} else {
mHandler.networkError();
}
2010-07-10 12:41:48 -04:00
if ((MessageView.this.mMessage == null) ||
!MessageView.this.mMessage.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
2012-04-04 04:04:57 -04:00
mMessageView.showStatusMessage(getString(R.string.webview_empty_message));
}
}
});
}
@Override
2011-02-14 20:45:08 -05:00
public void loadMessageForViewFinished(Account account, String folder, String uid, final Message message) {
if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder)
|| !mMessageReference.accountUuid.equals(account.getUuid())) {
return;
}
mHandler.post(new Runnable() {
public void run() {
2012-08-24 12:39:23 -04:00
setSupportProgressBarIndeterminateVisibility(false);
mMessageView.setShowDownloadButton(message);
}
});
}
@Override
public void loadMessageForViewStarted(Account account, String folder, String uid) {
if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder)
|| !mMessageReference.accountUuid.equals(account.getUuid())) {
return;
}
mHandler.post(new Runnable() {
public void run() {
2012-08-24 12:39:23 -04:00
setSupportProgressBarIndeterminateVisibility(true);
}
});
}
@Override
public void loadAttachmentStarted(Account account, Message message, Part part, Object tag, final boolean requiresDownload) {
if (mMessage != message) {
return;
}
mHandler.post(new Runnable() {
public void run() {
mMessageView.setAttachmentsEnabled(false);
showDialog(R.id.dialog_attachment_progress);
if (requiresDownload) {
mHandler.fetchingAttachment();
}
}
});
}
@Override
public void loadAttachmentFinished(Account account, Message message, Part part, final Object tag) {
if (mMessage != message) {
return;
}
mHandler.post(new Runnable() {
public void run() {
mMessageView.setAttachmentsEnabled(true);
removeDialog(R.id.dialog_attachment_progress);
Object[] params = (Object[]) tag;
boolean download = (Boolean) params[0];
AttachmentView attachment = (AttachmentView) params[1];
if (download) {
attachment.writeFile();
} else {
attachment.showFile();
}
}
});
}
@Override
2011-02-14 20:45:08 -05:00
public void loadAttachmentFailed(Account account, Message message, Part part, Object tag, String reason) {
if (mMessage != message) {
return;
}
mHandler.post(new Runnable() {
public void run() {
mMessageView.setAttachmentsEnabled(true);
removeDialog(R.id.dialog_attachment_progress);
mHandler.networkError();
}
});
}
}
// This REALLY should be in MessageCryptoView
public void onDecryptDone(PgpData pgpData) {
Account account = mAccount;
LocalMessage message = (LocalMessage) mMessage;
MessagingController controller = mController;
Listener listener = mListener;
try {
mMessageView.setMessage(account, message, pgpData, controller, listener);
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "displayMessageBody failed", e);
}
2010-07-27 08:10:09 -04:00
}
/**
* Set the title of the view. Since we're using a custom ActionBar view, the normal setTitle() doesn't do what we
* think. This version sets the text value into the proper ActionBar title view.
* @param title Title to set.
*/
@Override
public void setTitle(CharSequence title) {
mTitleView.setText(title);
}
}