2009-03-04 03:32:22 +00:00
|
|
|
/*
|
2010-07-26 21:35:06 +00:00
|
|
|
* Copyright (C) 2010 The Android Open Source Project
|
2009-03-04 03:32:22 +00:00
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.android.email.activity;
|
|
|
|
|
|
|
|
import com.android.email.Email;
|
|
|
|
import com.android.email.R;
|
2010-09-02 01:40:10 +00:00
|
|
|
import com.android.email.provider.EmailContent.Mailbox;
|
2009-07-28 02:52:21 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
import android.content.Context;
|
|
|
|
import android.content.Intent;
|
|
|
|
import android.os.Bundle;
|
2010-07-30 00:06:00 +00:00
|
|
|
import android.util.Log;
|
2009-03-04 03:32:22 +00:00
|
|
|
import android.view.Menu;
|
|
|
|
import android.view.MenuItem;
|
|
|
|
import android.view.View;
|
|
|
|
|
2010-07-26 21:35:06 +00:00
|
|
|
/**
|
|
|
|
* Activity to show (non-EML) email messages.
|
|
|
|
*
|
|
|
|
* This activity shows regular email messages, which are not file-based. (i.e. not *.eml or *.msg)
|
|
|
|
*
|
2010-07-30 00:06:00 +00:00
|
|
|
* See {@link MessageViewBase} for the class relation diagram.
|
2010-07-26 21:35:06 +00:00
|
|
|
*/
|
|
|
|
public class MessageView extends MessageViewBase implements View.OnClickListener,
|
2010-07-30 22:41:40 +00:00
|
|
|
MessageOrderManager.Callback, MessageViewFragment.Callback {
|
2009-06-15 23:46:38 +00:00
|
|
|
private static final String EXTRA_MESSAGE_ID = "com.android.email.MessageView_message_id";
|
2009-08-24 22:00:45 +00:00
|
|
|
private static final String EXTRA_MAILBOX_ID = "com.android.email.MessageView_mailbox_id";
|
2009-09-05 00:24:26 +00:00
|
|
|
|
|
|
|
// for saveInstanceState()
|
|
|
|
private static final String STATE_MESSAGE_ID = "messageId";
|
2009-09-08 21:34:07 +00:00
|
|
|
|
2009-06-15 23:46:38 +00:00
|
|
|
private long mMessageId;
|
2009-08-24 22:00:45 +00:00
|
|
|
private long mMailboxId;
|
2009-06-15 23:46:38 +00:00
|
|
|
|
2010-07-01 01:22:41 +00:00
|
|
|
private MessageOrderManager mOrderManager;
|
|
|
|
|
2010-07-30 22:41:40 +00:00
|
|
|
private MessageViewFragment mFragment;
|
2010-07-30 00:06:00 +00:00
|
|
|
|
2009-12-09 17:03:09 +00:00
|
|
|
private View mMoveToNewer;
|
|
|
|
private View mMoveToOlder;
|
2009-08-24 22:00:45 +00:00
|
|
|
|
2010-09-02 01:40:10 +00:00
|
|
|
// False when a message can't be forwarded/replied, such as trashed messages
|
|
|
|
private boolean mReplyAndForwardEnabled;
|
2009-10-19 16:55:50 +00:00
|
|
|
|
2009-06-15 23:46:38 +00:00
|
|
|
/**
|
|
|
|
* View a specific message found in the Email provider.
|
2009-08-24 22:00:45 +00:00
|
|
|
* @param messageId the message to view.
|
2009-12-09 17:03:09 +00:00
|
|
|
* @param mailboxId identifies the sequence of messages used for newer/older navigation.
|
2009-06-15 23:46:38 +00:00
|
|
|
*/
|
2010-09-02 01:40:10 +00:00
|
|
|
public static void actionView(Context context, long messageId, long mailboxId) {
|
2010-09-23 16:19:44 +00:00
|
|
|
context.startActivity(getActionViewIntent(context, messageId, mailboxId));
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Intent getActionViewIntent(Context context, long messageId, long mailboxId) {
|
2010-03-15 14:05:58 +00:00
|
|
|
if (messageId < 0) {
|
|
|
|
throw new IllegalArgumentException("MessageView invalid messageId " + messageId);
|
|
|
|
}
|
2009-06-15 23:46:38 +00:00
|
|
|
Intent i = new Intent(context, MessageView.class);
|
|
|
|
i.putExtra(EXTRA_MESSAGE_ID, messageId);
|
2009-08-24 22:00:45 +00:00
|
|
|
i.putExtra(EXTRA_MAILBOX_ID, mailboxId);
|
2010-09-23 16:19:44 +00:00
|
|
|
return i;
|
2009-06-15 23:46:38 +00:00
|
|
|
}
|
|
|
|
|
2010-07-30 00:06:00 +00:00
|
|
|
@Override
|
|
|
|
protected int getLayoutId() {
|
|
|
|
return R.layout.message_view;
|
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
@Override
|
|
|
|
public void onCreate(Bundle icicle) {
|
|
|
|
super.onCreate(icicle);
|
2010-04-06 17:25:15 +00:00
|
|
|
|
2010-07-30 22:41:40 +00:00
|
|
|
mFragment = (MessageViewFragment) findFragmentById(R.id.message_view_fragment);
|
2010-07-30 00:06:00 +00:00
|
|
|
mFragment.setCallback(this);
|
|
|
|
|
Let MessageViewFragment own bottom buttons.
Create a custom view containing the bottons below MVF
(delete, move, reply, etc) and let MVF own this.
These buttons used to be owned by the XL activity itself, because
the UI for these commands will most likely be totally different
from the tablet UI, so the fragment having them looked wrong.
However, this made it harder to make changes suggested by the latest
mock, such as "put reply/forward in the message header".
I think the buttons are semantically part of the message view anyway,
so the fragment owning UI for these commands is probably the way to go.
(And let's worry about the phone UI later.)
Reason for the use of a custom view is that it will make it easier
to make non-trivial UI changes, e.g. "combine reply, reply-all and
forward and make it dropdown."
Also removed obsolete TODOs from MessageListXL.
Change-Id: Ibf93f4c70fe07bdbbe33d2adb6bbd2b96812830d
2010-09-16 18:16:46 +00:00
|
|
|
// TODO Remove these bottom buttons, and make use of the ones in MessageViewFragments.
|
|
|
|
|
2009-12-09 17:03:09 +00:00
|
|
|
mMoveToNewer = findViewById(R.id.moveToNewer);
|
|
|
|
mMoveToOlder = findViewById(R.id.moveToOlder);
|
|
|
|
mMoveToNewer.setOnClickListener(this);
|
|
|
|
mMoveToOlder.setOnClickListener(this);
|
2010-07-16 22:10:00 +00:00
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
findViewById(R.id.reply).setOnClickListener(this);
|
|
|
|
findViewById(R.id.reply_all).setOnClickListener(this);
|
|
|
|
findViewById(R.id.delete).setOnClickListener(this);
|
|
|
|
|
2009-10-19 16:55:50 +00:00
|
|
|
initFromIntent();
|
2009-09-05 00:24:26 +00:00
|
|
|
if (icicle != null) {
|
|
|
|
mMessageId = icicle.getLong(STATE_MESSAGE_ID, mMessageId);
|
|
|
|
}
|
2009-08-24 22:00:45 +00:00
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
|
2010-07-26 21:35:06 +00:00
|
|
|
private void initFromIntent() {
|
2009-10-19 16:55:50 +00:00
|
|
|
Intent intent = getIntent();
|
2010-07-26 21:35:06 +00:00
|
|
|
mMessageId = intent.getLongExtra(EXTRA_MESSAGE_ID, -1);
|
|
|
|
mMailboxId = intent.getLongExtra(EXTRA_MAILBOX_ID, -1);
|
2010-07-30 00:06:00 +00:00
|
|
|
if (mMessageId == -1 || mMailboxId == -1) {
|
|
|
|
Log.w(Email.LOG_TAG, "Insufficient intent parameter. Closing...");
|
|
|
|
finish();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-09-02 01:40:10 +00:00
|
|
|
enableForwardReply(false);
|
2009-10-19 16:55:50 +00:00
|
|
|
}
|
|
|
|
|
2009-09-05 00:24:26 +00:00
|
|
|
@Override
|
|
|
|
protected void onSaveInstanceState(Bundle state) {
|
|
|
|
super.onSaveInstanceState(state);
|
|
|
|
if (mMessageId != -1) {
|
|
|
|
state.putLong(STATE_MESSAGE_ID, mMessageId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
@Override
|
|
|
|
public void onResume() {
|
|
|
|
super.onResume();
|
2010-04-14 16:28:04 +00:00
|
|
|
|
2010-07-26 21:35:06 +00:00
|
|
|
// Exit immediately if the accounts list has changed (e.g. externally deleted)
|
|
|
|
if (Email.getNotifyUiAccountsChanged()) {
|
|
|
|
Welcome.actionStart(this);
|
|
|
|
finish();
|
|
|
|
return;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-07-26 21:35:06 +00:00
|
|
|
mOrderManager = new MessageOrderManager(this, mMailboxId, this);
|
2010-07-16 22:10:00 +00:00
|
|
|
messageChanged();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPause() {
|
2010-07-01 01:22:41 +00:00
|
|
|
if (mOrderManager != null) {
|
|
|
|
mOrderManager.close();
|
|
|
|
mOrderManager = null;
|
2009-09-02 23:49:24 +00:00
|
|
|
}
|
2010-07-01 01:22:41 +00:00
|
|
|
super.onPause();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2010-07-30 00:06:00 +00:00
|
|
|
// Note the return type is a subclass of that of the super class method.
|
|
|
|
@Override
|
2010-07-30 22:41:40 +00:00
|
|
|
protected MessageViewFragment getFragment() {
|
2010-07-30 00:06:00 +00:00
|
|
|
return mFragment;
|
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
@Override
|
2010-07-26 21:35:06 +00:00
|
|
|
protected long getAccountId() {
|
|
|
|
return getFragment().getAccountId();
|
2009-07-01 19:11:47 +00:00
|
|
|
}
|
|
|
|
|
2010-09-02 01:40:10 +00:00
|
|
|
@Override
|
|
|
|
public void onMessageViewShown(int mailboxType) {
|
|
|
|
super.onMessageViewShown(mailboxType);
|
|
|
|
enableForwardReply(mailboxType != Mailbox.TYPE_TRASH);
|
|
|
|
}
|
|
|
|
|
2010-06-30 00:17:12 +00:00
|
|
|
private void onDeleteMessage() {
|
2010-07-16 22:10:00 +00:00
|
|
|
// the delete triggers mCursorObserver in MessageOrderManager.
|
|
|
|
// first move to older/newer before the actual delete
|
|
|
|
long messageIdToDelete = mMessageId;
|
2010-09-09 22:34:19 +00:00
|
|
|
boolean moved = moveToOlder() || moveToNewer(); // TODO use "auto-advance" preference
|
2010-08-17 22:07:28 +00:00
|
|
|
ActivityHelper.deleteMessage(this, messageIdToDelete);
|
2010-07-16 22:10:00 +00:00
|
|
|
if (!moved) {
|
|
|
|
// this generates a benign warning "Duplicate finish request" because
|
|
|
|
// MessageOrderManager detects that the current message is gone, and we finish() it
|
|
|
|
// in the onMessageNotFound() callback.
|
|
|
|
finish();
|
2010-06-30 00:17:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-12-09 17:03:09 +00:00
|
|
|
private boolean moveToOlder() {
|
2010-07-01 01:22:41 +00:00
|
|
|
if (mOrderManager != null && mOrderManager.moveToOlder()) {
|
|
|
|
mMessageId = mOrderManager.getCurrentMessageId();
|
2009-08-24 22:00:45 +00:00
|
|
|
messageChanged();
|
|
|
|
return true;
|
|
|
|
}
|
2009-09-05 00:24:26 +00:00
|
|
|
return false;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
2009-12-09 17:03:09 +00:00
|
|
|
private boolean moveToNewer() {
|
2010-07-01 01:22:41 +00:00
|
|
|
if (mOrderManager != null && mOrderManager.moveToNewer()) {
|
|
|
|
mMessageId = mOrderManager.getCurrentMessageId();
|
2009-08-24 22:00:45 +00:00
|
|
|
messageChanged();
|
|
|
|
return true;
|
|
|
|
}
|
2009-09-05 00:24:26 +00:00
|
|
|
return false;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void onClick(View view) {
|
|
|
|
switch (view.getId()) {
|
|
|
|
case R.id.reply:
|
|
|
|
onReply();
|
|
|
|
break;
|
|
|
|
case R.id.reply_all:
|
|
|
|
onReplyAll();
|
|
|
|
break;
|
|
|
|
case R.id.delete:
|
2010-06-30 00:17:12 +00:00
|
|
|
onDeleteMessage();
|
2009-03-04 03:32:22 +00:00
|
|
|
break;
|
2009-12-09 17:03:09 +00:00
|
|
|
case R.id.moveToOlder:
|
|
|
|
moveToOlder();
|
2009-03-04 03:32:22 +00:00
|
|
|
break;
|
2009-12-09 17:03:09 +00:00
|
|
|
case R.id.moveToNewer:
|
|
|
|
moveToNewer();
|
2009-03-04 03:32:22 +00:00
|
|
|
break;
|
2010-07-16 22:10:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
|
|
boolean handled = handleMenuItem(item.getItemId());
|
|
|
|
if (!handled) {
|
|
|
|
handled = super.onOptionsItemSelected(item);
|
|
|
|
}
|
|
|
|
return handled;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This is the core functionality of onOptionsItemSelected() but broken out and exposed
|
|
|
|
* for testing purposes (because it's annoying to mock a MenuItem).
|
|
|
|
*
|
|
|
|
* @param menuItemId id that was clicked
|
|
|
|
* @return true if handled here
|
|
|
|
*/
|
|
|
|
/* package */ boolean handleMenuItem(int menuItemId) {
|
|
|
|
switch (menuItemId) {
|
|
|
|
case R.id.delete:
|
|
|
|
onDeleteMessage();
|
2009-03-04 03:32:22 +00:00
|
|
|
break;
|
2010-07-16 22:10:00 +00:00
|
|
|
case R.id.reply:
|
|
|
|
onReply();
|
2010-02-17 06:02:50 +00:00
|
|
|
break;
|
2010-07-16 22:10:00 +00:00
|
|
|
case R.id.reply_all:
|
|
|
|
onReplyAll();
|
2010-02-17 06:02:50 +00:00
|
|
|
break;
|
2010-07-16 22:10:00 +00:00
|
|
|
case R.id.forward:
|
|
|
|
onForward();
|
2010-02-24 18:24:13 +00:00
|
|
|
break;
|
2010-07-16 22:10:00 +00:00
|
|
|
case R.id.mark_as_unread:
|
2010-07-26 21:35:06 +00:00
|
|
|
getFragment().onMarkMessageAsRead(false);
|
2010-02-17 06:02:50 +00:00
|
|
|
break;
|
2010-07-16 22:10:00 +00:00
|
|
|
default:
|
|
|
|
return false;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2010-07-16 22:10:00 +00:00
|
|
|
return true;
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-09-08 21:34:07 +00:00
|
|
|
|
2010-07-16 22:10:00 +00:00
|
|
|
@Override
|
|
|
|
public void onMessageSetUnread() {
|
|
|
|
finish();
|
2010-06-30 00:17:12 +00:00
|
|
|
}
|
|
|
|
|
2010-09-02 01:40:10 +00:00
|
|
|
private void enableForwardReply(boolean enabled) {
|
|
|
|
mReplyAndForwardEnabled = enabled;
|
|
|
|
findViewById(R.id.reply).setEnabled(enabled);
|
|
|
|
findViewById(R.id.reply_all).setEnabled(enabled);
|
|
|
|
}
|
|
|
|
|
2009-03-04 03:32:22 +00:00
|
|
|
@Override
|
|
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
|
|
super.onCreateOptionsMenu(menu);
|
|
|
|
getMenuInflater().inflate(R.menu.message_view_option, menu);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-09-02 01:40:10 +00:00
|
|
|
@Override
|
|
|
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
|
|
|
menu.findItem(R.id.forward).setEnabled(mReplyAndForwardEnabled);
|
|
|
|
menu.findItem(R.id.reply).setEnabled(mReplyAndForwardEnabled);
|
|
|
|
menu.findItem(R.id.reply_all).setEnabled(mReplyAndForwardEnabled);
|
|
|
|
return super.onPrepareOptionsMenu(menu);
|
|
|
|
}
|
|
|
|
|
2010-07-26 21:35:06 +00:00
|
|
|
/**
|
|
|
|
* Sync the current message.
|
|
|
|
* - Set message id to the fragment and the message order manager.
|
|
|
|
* - Update the navigation arrows.
|
|
|
|
*/
|
2009-09-02 23:49:24 +00:00
|
|
|
private void messageChanged() {
|
2010-07-26 21:35:06 +00:00
|
|
|
getFragment().openMessage(mMessageId);
|
|
|
|
if (mOrderManager != null) {
|
|
|
|
mOrderManager.moveTo(mMessageId);
|
2009-09-28 14:47:25 +00:00
|
|
|
}
|
2010-07-01 01:22:41 +00:00
|
|
|
updateNavigationArrows();
|
2009-09-02 23:49:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2009-12-09 17:03:09 +00:00
|
|
|
* Update the arrows based on the current position of the older/newer cursor.
|
2009-09-02 23:49:24 +00:00
|
|
|
*/
|
2010-07-01 01:22:41 +00:00
|
|
|
private void updateNavigationArrows() {
|
2010-07-16 22:10:00 +00:00
|
|
|
mMoveToNewer.setEnabled((mOrderManager != null) && mOrderManager.canMoveToNewer());
|
|
|
|
mMoveToOlder.setEnabled((mOrderManager != null) && mOrderManager.canMoveToOlder());
|
2009-09-02 23:49:24 +00:00
|
|
|
}
|
|
|
|
|
2010-07-16 22:10:00 +00:00
|
|
|
/** Implements {@link MessageOrderManager.Callback#onMessageNotFound()}. */
|
|
|
|
// TODO Name too generic. Rename this.
|
|
|
|
@Override
|
|
|
|
public void onMessageNotFound() {
|
|
|
|
finish();
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|
2009-09-08 21:34:07 +00:00
|
|
|
|
2010-07-16 22:10:00 +00:00
|
|
|
/** Implements {@link MessageOrderManager.Callback#onMessagesChanged()}. */
|
|
|
|
// TODO Name too generic. Rename this.
|
|
|
|
@Override
|
|
|
|
public void onMessagesChanged() {
|
|
|
|
updateNavigationArrows();
|
2009-06-15 23:46:38 +00:00
|
|
|
}
|
2009-09-08 21:34:07 +00:00
|
|
|
|
2010-07-01 01:22:41 +00:00
|
|
|
@Override
|
2010-07-16 22:10:00 +00:00
|
|
|
public void onRespondedToInvite(int response) {
|
2010-09-10 00:23:53 +00:00
|
|
|
// TODO use "auto-advance" preference
|
2010-07-16 22:10:00 +00:00
|
|
|
if (!moveToOlder()) {
|
|
|
|
finish(); // if this is the last message, move up to message-list.
|
|
|
|
}
|
2010-07-01 01:22:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2010-07-16 22:10:00 +00:00
|
|
|
public void onCalendarLinkClicked(long epochEventStartTime) {
|
2010-08-17 22:07:28 +00:00
|
|
|
ActivityHelper.openCalendar(this, epochEventStartTime);
|
2010-07-01 01:22:41 +00:00
|
|
|
}
|
Let MessageViewFragment own bottom buttons.
Create a custom view containing the bottons below MVF
(delete, move, reply, etc) and let MVF own this.
These buttons used to be owned by the XL activity itself, because
the UI for these commands will most likely be totally different
from the tablet UI, so the fragment having them looked wrong.
However, this made it harder to make changes suggested by the latest
mock, such as "put reply/forward in the message header".
I think the buttons are semantically part of the message view anyway,
so the fragment owning UI for these commands is probably the way to go.
(And let's worry about the phone UI later.)
Reason for the use of a custom view is that it will make it easier
to make non-trivial UI changes, e.g. "combine reply, reply-all and
forward and make it dropdown."
Also removed obsolete TODOs from MessageListXL.
Change-Id: Ibf93f4c70fe07bdbbe33d2adb6bbd2b96812830d
2010-09-16 18:16:46 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onReply() {
|
|
|
|
MessageCompose.actionReply(this, mMessageId, false);
|
|
|
|
finish();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onReplyAll() {
|
|
|
|
MessageCompose.actionReply(this, mMessageId, true);
|
|
|
|
finish();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onForward() {
|
|
|
|
MessageCompose.actionForward(this, mMessageId);
|
|
|
|
finish();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onMoveToNewer() {
|
|
|
|
// TODO Implement this
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onMoveToOlder() {
|
|
|
|
// TODO Implement this
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onBeforeMessageDelete() {
|
|
|
|
// TODO Implement this
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onMoveMessage() {
|
|
|
|
// TODO Implement this
|
|
|
|
}
|
2009-03-04 03:32:22 +00:00
|
|
|
}
|