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

1162 lines
41 KiB
Java

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();
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 {
@Override
public void onUnmount(String providerId) {
if (!providerId.equals(mAccount.getLocalStorageProviderId())) {
return;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
onAccountUnavailable();
}
});
}
@Override
public void onMount(String providerId) { /* no-op */ }
}
@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;
}
}
return super.onKeyUp(keyCode, event);
}
class MessageViewHandler extends Handler {
public void progress(final boolean progress) {
runOnUiThread(new Runnable() {
public void run() {
setSupportProgressBarIndeterminateVisibility(progress);
}
});
}
public void addAttachment(final View attachmentView) {
runOnUiThread(new Runnable() {
public void run() {
mMessageView.addAttachment(attachmentView);
}
});
}
/* 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() {
Toast.makeText(MessageView.this, message, toastLength).show();
}
});
}
public void networkError() {
showToast(getString(R.string.status_network_error), Toast.LENGTH_LONG);
}
public void invalidIdError() {
showToast(getString(R.string.status_invalid_id_error), Toast.LENGTH_LONG);
}
public void fetchingAttachment() {
showToast(getString(R.string.message_view_fetching_attachment_toast), Toast.LENGTH_SHORT);
}
}
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);
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);
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) {
//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;
}
}
if (!found) {
//TODO: Use resource to externalize message
Toast.makeText(this, "Invalid account id: " + accountId, Toast.LENGTH_LONG).show();
return;
}
mMessageReference = new MessageReference();
mMessageReference.accountUuid = mAccount.getUuid();
mMessageReference.folderName = segmentList.get(1);
mMessageReference.uid = segmentList.get(2);
mMessageReferences = new ArrayList<MessageReference>();
}
}
mAccount = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid);
mScreenWidthInPixels = getResources().getDisplayMetrics().widthPixels;
// Enable gesture detection for MessageViews
mGestureDetector = new GestureDetector(new MyGestureDetector(false));
displayMessage(mMessageReference);
}
@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);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mPgpData = (PgpData) savedInstanceState.getSerializable(STATE_PGP_DATA);
mMessageView.updateCryptoLayout(mAccount.getCryptoProvider(), mPgpData, mMessage);
}
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);
}
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)) {
onAccountUnavailable();
return;
}
StorageManager.getInstance(getApplication()).addListener(mStorageListener);
}
@Override
protected void onPause() {
StorageManager.getInstance(getApplication()).removeListener(mStorageListener);
super.onPause();
}
protected void onAccountUnavailable() {
finish();
// TODO inform user about account unavailability using Toast
Accounts.listAccounts(this);
}
/**
* 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();
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();
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;
}
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;
}
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);
startActivityForResult(intent, activity);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mAccount.getCryptoProvider().onActivityResult(this, requestCode, resultCode, data, mPgpData)) {
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();
mController.moveMessage(mAccount, srcFolderName, messageToMove, destFolderName, null);
break;
case ACTIVITY_CHOOSE_FOLDER_COPY:
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) {
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) {
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)) {
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:
onNext();
break;
case R.id.previous_message:
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;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getSupportMenuInflater().inflate(R.menu.message_view_option, menu);
mMenu = menu;
configureMenu(menu);
return true;
}
private void configureMenu(Menu menu) {
// first run displayMessage() gets called before onCreateOptionMenu()
if (menu == null) {
return;
}
// 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);
if (mNextMessage != null) {
menu.findItem(R.id.next_message).setEnabled(true);
menu.findItem(R.id.next_message).getIcon().setAlpha(255);
} else {
menu.findItem(R.id.next_message).getIcon().setAlpha(127);
menu.findItem(R.id.next_message).setEnabled(false);
}
if (mPreviousMessage != null) {
menu.findItem(R.id.previous_message).setEnabled(true);
menu.findItem(R.id.previous_message).getIcon().setAlpha(255);
} else {
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 {
// 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(
!mMessageReference.folderName.equals(mAccount.getArchiveFolderName())
&& mAccount.hasArchiveFolder());
menu.findItem(R.id.spam).setVisible(
!mMessageReference.folderName.equals(mAccount.getSpamFolderName())
&& mAccount.hasSpamFolder());
} else {
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);
}
}
}
private void toggleActionsState(Menu menu, boolean state) {
for (int i = 0; i < menu.size(); ++i) {
menu.getItem(i).setEnabled(state);
}
}
// TODO: when switching to API version 8, override onCreateDialog(int, Bundle)
/**
* @param id The id of the dialog.
* @return The dialog. If you return null, the dialog will not be created.
* @see android.app.Activity#onCreateDialog(int)
*/
@Override
protected Dialog onCreateDialog(final int id) {
switch (id) {
case R.id.dialog_confirm_delete:
return ConfirmationDialog.create(this, id,
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,
R.string.dialog_confirm_spam_title,
getResources().getQuantityString(R.plurals.dialog_confirm_spam_message, 1),
R.string.dialog_confirm_spam_confirm_button,
R.string.dialog_confirm_spam_cancel_button,
new Runnable() {
@Override
public void run() {
refileMessage(mDstFolder);
mDstFolder = null;
}
});
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)) {
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());
}
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
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() {
setSupportProgressBarIndeterminateVisibility(false);
if (t instanceof IllegalArgumentException) {
mHandler.invalidIdError();
} else {
mHandler.networkError();
}
if ((MessageView.this.mMessage == null) ||
!MessageView.this.mMessage.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
mMessageView.showStatusMessage(getString(R.string.webview_empty_message));
}
}
});
}
@Override
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() {
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() {
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
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);
}
}
/**
* 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);
}
}