Refactoring MessageListXL

I always thought our Activities are way too fat, meaning we've put too many
things into activities without any structure.

The major problems with this are:
- They have too many fields, which are not final and not even orthogonal.
  This makes them very hard to understand/maintain.  Changing one tiny bit
  can always cause unanticipated side-effects.
- Very hard, or almost impossible to test.

I really think we should break them into independent and self-contained
subcomponents which can be tested separately.

Introducing MessageListXLStateManager, which manages the current account,
mailbox and message, and show/hide/update fragments accordingly
for MessageListXL.

With this class, MessageListXL will be able to switch accounts/mailboxes/
messages by just calling the methods such as selectAccount(), without
worrying about when to show/hide what fragment and how to initialize them.
(In other words, MessageListXLStateManager encapsulates the two-pane screen
transition.  It's not intended to be reused for the phone UI.)

I didn't make it a nested class in MessageListXL, because nested classes can't
have real private members (private member are accessible from outer classes and
even brother classes!!), and I wanted it to be really self-contained anyway.

Change-Id: I1c121e99e30f12cc118e1c35abc9b30f49939a4a
This commit is contained in:
Makoto Onuki 2010-07-22 15:01:31 -07:00
parent f08b061ea8
commit 02fa70aa18
6 changed files with 675 additions and 338 deletions

View File

@ -51,7 +51,12 @@ public class Email extends Application {
public static boolean DEBUG = false;
/**
* If this is enabled than logging that normally hides sensitive information
* If true, logging regarding activity/fragment lifecycle will be enabled.
*/
public static final boolean DEBUG_LIFECYCLE = true; // STOPSHIP Turn this off.
/**
* If this is enabled then logging that normally hides sensitive information
* like passwords will show that information.
*/
public static final boolean DEBUG_SENSITIVE = false;

View File

@ -97,7 +97,9 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
*/
@Override
public void onCreate(Bundle savedInstanceState) {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MailboxListFragment onCreate");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MailboxListFragment onCreate");
}
super.onCreate(savedInstanceState);
mActivity = getActivity();
@ -106,7 +108,9 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
@Override
public void onActivityCreated(Bundle savedInstanceState) {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MailboxListFragment onActivityCreated");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MailboxListFragment onActivityCreated");
}
super.onActivityCreated(savedInstanceState);
ListView listView = getListView();
@ -123,7 +127,9 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
* @param accountId the account we're looking at
*/
public void openMailboxes(long accountId) {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MailboxListFragment openMailboxes");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MailboxListFragment openMailboxes");
}
if (accountId == -1) {
throw new InvalidParameterException();
}
@ -141,7 +147,9 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
*/
@Override
public void onStart() {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MailboxListFragment onStart");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MailboxListFragment onStart");
}
super.onStart();
mStarted = true;
if (mAccountId != -1) {
@ -154,7 +162,9 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
*/
@Override
public void onResume() {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MailboxListFragment onResume");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MailboxListFragment onResume");
}
mStarted = false;
super.onResume();
updateMessageCount();
@ -165,7 +175,9 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
*/
@Override
public void onStop() {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MailboxListFragment onStop");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MailboxListFragment onStop");
}
super.onStop();
cancelAllTasks();
}
@ -175,7 +187,9 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
*/
@Override
public void onDestroy() {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MailboxListFragment onDestroy");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MailboxListFragment onDestroy");
}
super.onDestroy();
mListAdapter.changeCursor(null);
@ -183,7 +197,9 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
@Override
public void onSaveInstanceState(Bundle outState) {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MailboxListFragment onSaveInstanceState");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MailboxListFragment onSaveInstanceState");
}
super.onSaveInstanceState(outState);
}

View File

@ -136,7 +136,9 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
@Override
public void onCreate(Bundle savedInstanceState) {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListFragment onCreate");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListFragment onCreate");
}
super.onCreate(savedInstanceState);
mActivity = getActivity();
mResolver = mActivity.getContentResolver();
@ -146,7 +148,9 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
@Override
public void onActivityCreated(Bundle savedInstanceState) {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListFragment onActivityCreated");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListFragment onActivityCreated");
}
super.onActivityCreated(savedInstanceState);
ListView listView = getListView();
@ -169,7 +173,9 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
@Override
public void onStart() {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListFragment onStart");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListFragment onStart");
}
super.onStart();
mStarted = true;
if (mMailboxId != -1) {
@ -179,14 +185,18 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
@Override
public void onStop() {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListFragment onStop");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListFragment onStop");
}
mStarted = false;
super.onStop();
}
@Override
public void onResume() {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListFragment onResume");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListFragment onResume");
}
super.onResume();
restoreListPosition();
autoRefreshStaleMailbox();
@ -194,7 +204,9 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
@Override
public void onDestroy() {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListFragment onDestroy");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListFragment onDestroy");
}
Utility.cancelTaskInterrupt(mLoadMessagesTask);
mLoadMessagesTask = null;
Utility.cancelTaskInterrupt(mSetFooterTask);
@ -208,7 +220,9 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
@Override
public void onSaveInstanceState(Bundle outState) {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListFragment onSaveInstanceState");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListFragment onSaveInstanceState");
}
super.onSaveInstanceState(outState);
saveListPosition();
outState.putInt(STATE_SELECTED_POSITION, mSavedItemPosition);
@ -249,7 +263,9 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
* {@link Mailbox#QUERY_ALL_INBOXES}. -1 is not allowed.
*/
public void openMailbox(long accountId, long mailboxId) {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListFragment openMailbox");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListFragment openMailbox");
}
if (mailboxId == -1) {
throw new InvalidParameterException();
}

View File

@ -21,7 +21,6 @@ import com.android.email.R;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context;
import android.content.Loader;
@ -31,179 +30,182 @@ import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import java.util.ArrayList;
// TODO Where/when/how do we close loaders?? Do we have to? Getting this error:
// Finalizing a Cursor that has not been deactivated or closed.
// database = /data/data/com.google.android.email/databases/EmailProvider.db,
// table = Account, query = SELECT _id, displayName, emailAddress FROM Account
/**
* Two pane activity for XL screen devices.
*
* TOOD Test it!
* The main (two-pane) activity for XL devices.
*/
public class MessageListXL extends Activity implements View.OnClickListener {
private static final String BUNDLE_KEY_ACCOUNT_ID = "MessageListXl.account_id";
private static final String BUNDLE_KEY_MAILBOX_ID = "MessageListXl.mailbox_id";
private static final String BUNDLE_KEY_MESSAGE_ID = "MessageListXl.message_id";
public class MessageListXL extends Activity implements View.OnClickListener,
MessageListXLFragmentManager.TargetActivity {
private static final int LOADER_ID_DEFAULT_ACCOUNT = 0;
private Context mContext;
private long mAccountId = -1;
private long mMailboxId = -1;
private long mMessageId = -1;
private View mMessageViewButtonPanel;
private View mMoveToNewerButton;
private View mMoveToOlderButton;
private MailboxListFragment mMailboxListFragment;
private MessageListFragment mMessageListFragment;
private MessageViewFragment mMessageViewFragment;
private View mMessageViewButtons;
private View mMoveToNewer;
private View mMoveToOlder;
private boolean mActivityInitialized;
private final ArrayList<Fragment> mRestoredFragments = new ArrayList<Fragment>();
private MessageOrderManager mOrderManager;
private final MessageListXLFragmentManager mFragmentManager
= new MessageListXLFragmentManager(this);
private final MessageOrderManagerCallback mMessageOrderManagerCallback
= new MessageOrderManagerCallback();
@Override
protected void onCreate(Bundle savedInstanceState) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListXL onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.message_list_xl);
final boolean isRestoring = (savedInstanceState != null);
mContext = getApplicationContext();
mMessageViewButtons = findViewById(R.id.message_view_buttons);
mMoveToNewer = findViewById(R.id.moveToNewer);
mMoveToOlder = findViewById(R.id.moveToOlder);
mMoveToNewer.setOnClickListener(this);
mMoveToOlder.setOnClickListener(this);
mFragmentManager.setMailboxListFragmentCallback(new MailboxListFragmentCallback());
mFragmentManager.setMessageListFragmentCallback(new MessageListFragmentCallback());
mFragmentManager.setMessageViewFragmentCallback(new MessageViewFragmentCallback());
mMessageViewButtonPanel = findViewById(R.id.message_view_buttons);
mMoveToNewerButton = findViewById(R.id.moveToNewer);
mMoveToOlderButton = findViewById(R.id.moveToOlder);
mMoveToNewerButton.setOnClickListener(this);
mMoveToOlderButton.setOnClickListener(this);
if (isRestoring) {
restoreSavedState(savedInstanceState);
mFragmentManager.loadState(savedInstanceState);
}
if (mAccountId == -1) {
if (!mFragmentManager.isAccountSelected()) {
loadDefaultAccount();
}
// TODO Initialize accounts dropdown.
mActivityInitialized = true;
if (isRestoring) {
initRestoredFragments();
}
}
private void restoreSavedState(Bundle savedInstanceState) {
mAccountId = savedInstanceState.getLong(BUNDLE_KEY_ACCOUNT_ID, -1);
mMailboxId = savedInstanceState.getLong(BUNDLE_KEY_MAILBOX_ID, -1);
mMessageId = savedInstanceState.getLong(BUNDLE_KEY_MESSAGE_ID, -1);
if (Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListXl: Restoring "
+ mAccountId + "," + mMailboxId + "," + mMessageId);
}
// TODO load account list and show account selector
}
@Override
protected void onSaveInstanceState(Bundle outState) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListXL onSaveInstanceState");
}
super.onSaveInstanceState(outState);
outState.putLong(BUNDLE_KEY_ACCOUNT_ID, mAccountId);
outState.putLong(BUNDLE_KEY_MAILBOX_ID, mMailboxId);
outState.putLong(BUNDLE_KEY_MESSAGE_ID, mMessageId);
mFragmentManager.onSaveInstanceState(outState);
}
@Override
protected void onStart() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListXL onStart");
super.onStart();
startMessageOrderManager();
mFragmentManager.setStart();
if (mFragmentManager.isMessageSelected()) {
updateMessageOrderManager();
}
}
@Override
protected void onResume() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListXL onResume");
super.onResume();
// TODO Add stuff that's done in MessageList.onResume().
}
@Override
protected void onPause() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListXL onPause");
super.onPause();
}
@Override
protected void onStop() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListXL onStop");
super.onStop();
mFragmentManager.onStop();
stopMessageOrderManager();
}
@Override
protected void onDestroy() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListXL onDestroy");
super.onDestroy();
}
@Override
public void onAttachFragment(Fragment fragment) {
if (!mActivityInitialized) {
// Fragments are being restored in super.onCreate().
// We can't initialize fragments until the activity is initialized, so remember them for
// now, and initialize them later in initRestoredFragments().
mRestoredFragments.add(fragment);
return;
}
if (Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListXl.onAttachFragment fragment=" + fragment.getClass());
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListXL onAttachFragment " + fragment.getClass());
}
super.onAttachFragment(fragment);
initFragment(fragment);
mFragmentManager.onAttachFragment(fragment);
}
private void initFragment(Fragment fragment) {
if (fragment instanceof MailboxListFragment) {
initMailboxListFragment((MailboxListFragment) fragment);
} else if (fragment instanceof MessageListFragment) {
initMessageListFragment((MessageListFragment) fragment);
} else if (fragment instanceof MessageViewFragment) {
initMessageViewFragment((MessageViewFragment) fragment);
@Override
public void onBackPressed() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListXL onBackPressed");
}
if (mFragmentManager.isMessageSelected()) {
// Go back to the message list.
// TODO: This works for now, but it doesn't restore the list view state, e.g. scroll
// position.
// TODO: FragmentTransaction *does* support backstack, but the behavior isn't too clear
// at this point.
mFragmentManager.selectMailbox(mFragmentManager.getMailboxId());
} else {
// Perform the default behavior == close the activity.
super.onBackPressed();
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.moveToOlder:
moveToOlder();
break;
case R.id.moveToNewer:
moveToNewer();
break;
}
}
/**
* Called from {@link #onCreate}.
* Initializes the fragmetns that are restored in super.onCreate().
* Start {@link MessageOrderManager} if not started, and sync it to the current message.
*/
private void initRestoredFragments() {
for (Fragment f : mRestoredFragments) {
initFragment(f);
}
mRestoredFragments.clear();
}
private void startMessageOrderManager() {
if (mMailboxId == -1) {
private void updateMessageOrderManager() {
if (!mFragmentManager.isMailboxSelected()) {
return;
}
if (mOrderManager != null && mOrderManager.getMailboxId() == mMailboxId) {
return;
final long mailboxId = mFragmentManager.getMailboxId();
if (mOrderManager == null || mOrderManager.getMailboxId() != mailboxId) {
stopMessageOrderManager();
mOrderManager = new MessageOrderManager(this, mailboxId, mMessageOrderManagerCallback);
}
if (mFragmentManager.isMessageSelected()) {
mOrderManager.moveTo(mFragmentManager.getMessageId());
}
stopMessageOrderManager();
mOrderManager = new MessageOrderManager(this, mMailboxId,
new MessageOrderManager.Callback() {
@Override
public void onMessagesChanged() {
updateNavigationArrows();
}
@Override
public void onMessageNotFound() {
// Current message removed.
selectMailbox(mMailboxId);
}
});
}
private class MessageOrderManagerCallback implements MessageOrderManager.Callback {
@Override
public void onMessagesChanged() {
updateNavigationArrows();
}
@Override
public void onMessageNotFound() {
// TODO Current message gone
}
}
/**
* Stop {@link MessageOrderManager}.
*/
private void stopMessageOrderManager() {
if (mOrderManager != null) {
mOrderManager.close();
@ -226,12 +228,15 @@ public class MessageListXL extends Activity implements View.OnClickListener {
if (accountId == null || accountId == -1) {
onNoAccountFound();
} else {
selectAccount(accountId);
mFragmentManager.selectAccount(accountId);
}
}
});
}
/**
* Called when the default account is not found, i.e. there's no account set up.
*/
private void onNoAccountFound() {
// Open Welcome, which in turn shows the adding a new account screen.
Welcome.actionStart(this);
@ -239,234 +244,126 @@ public class MessageListXL extends Activity implements View.OnClickListener {
return;
}
// NOTE These selectXxx are *not* only methods where mXxxId's are changed.
// When the activity is re-created (e.g. for orientation change), the following things happen.
// - mXxxId's are restored from Bundle
// - Fragments are restored by the framework (in super.onCreate())
// - mXxxId's are set to fragments in initXxxFragment()
//
// So, if you want to do something when, for example, the current account changes,
// selectAccount() is probably not a good place to do it, because it'll be skipped when
// the activity is re-created.
// Instead, do that in initXxxFragment(). Alternatively, adding the same procedure to
// initRestoredFragments() too will probably work.
private void selectAccount(long accountId) {
// TODO Handle "combined mailboxes". Who should take care of it? This activity?
// MailboxListFragment??
if (Email.DEBUG) {
Log.d(Email.LOG_TAG, "selectAccount mAccountId=" + mAccountId);
}
if (accountId == -1) {
throw new RuntimeException();
}
if (mAccountId == accountId) {
return;
}
mAccountId = accountId;
mMailboxId = -1;
mMessageId = -1;
// TODO We don't have to replace the fragment, Just update it.
// That will be in accordance with our back model.
final MailboxListFragment fragment = new MailboxListFragment();
final FragmentTransaction ft = openFragmentTransaction();
ft.replace(R.id.left_pane, fragment);
if (mMessageListFragment != null) {
ft.remove(mMessageListFragment);
}
if (mMessageViewFragment != null) {
ft.remove(mMessageListFragment);
}
ft.commit();
// TODO Open inbox for the selected account.
}
private void selectMailbox(long mailboxId) {
if (Email.DEBUG) {
Log.d(Email.LOG_TAG, "selectMailbox mMailboxId=" + mMailboxId);
}
if (mMailboxId == mailboxId) {
return;
}
// TODO We don't have to replace the fragment, if it's already the message list. Just
// update it.
// That will be in accordance with our back model.
mMailboxId = mailboxId;
mMessageId = -1;
MessageListFragment fragment = new MessageListFragment();
openFragmentTransaction().replace(R.id.right_pane, fragment).commit();
}
private void selectMessage(long messageId) {
// TODO: Deal with draft messages. (open MessageCompose instead)
if (Email.DEBUG) {
Log.d(Email.LOG_TAG, "selectMessage messageId=" + mMessageId);
}
if (mMessageId == messageId) {
return;
}
mMessageId = messageId;
MessageViewFragment fragment = new MessageViewFragment();
openFragmentTransaction().replace(R.id.right_pane, fragment).commit();
}
private void initMailboxListFragment(MailboxListFragment fragment) {
if (Email.DEBUG) {
Log.d(Email.LOG_TAG, "initMailboxListFragment mAccountId=" + mAccountId);
}
if (mAccountId == -1) {
throw new RuntimeException();
}
mMessageListFragment = null;
mMessageViewFragment = null;
mMailboxListFragment = fragment;
fragment.setCallback(new MailboxListFragment.Callback() {
@Override
public void onRefresh(long accountId, long mailboxId) {
// Will be removed.
}
// TODO Rename to onSelectMailbox
@Override
public void onMailboxSelected(long accountId, long mailboxId) {
selectMailbox(mailboxId);
}
});
fragment.openMailboxes(mAccountId);
}
private void initMessageListFragment(MessageListFragment fragment) {
if (Email.DEBUG) {
Log.d(Email.LOG_TAG, "initMessageListFragment mMailboxId=" + mMailboxId);
}
if (mAccountId == -1 || mMailboxId == -1) {
throw new RuntimeException();
}
mMessageListFragment = fragment;
mMessageViewFragment = null;
mMessageViewButtons.setVisibility(View.GONE);
fragment.setCallback(new MessageListFragment.Callback() {
@Override
public void onSelectionChanged() {
// TODO Context mode
}
@Override
// TODO Rename to onSelectMessage
public void onMessageOpen(long messageId, long mailboxId) { // RENAME: OpenMessage ?
selectMessage(messageId);
}
@Override
public void onMailboxNotFound() { // RENAME: NotExists? (see MessageViewFragment)
// TODO: What to do??
}
});
fragment.openMailbox(mAccountId, mMailboxId);
}
private void initMessageViewFragment(MessageViewFragment fragment) {
if (Email.DEBUG) {
Log.d(Email.LOG_TAG, "initMessageViewFragment messageId=" + mMessageId);
}
if (mMessageId == -1) {
throw new RuntimeException();
}
mMessageViewFragment = fragment;
mMessageListFragment = null;
mMessageViewButtons.setVisibility(View.VISIBLE);
fragment.setCallback(new MessageViewFragment.Callback() {
@Override
public boolean onUrlInMessageClicked(String url) {
return false;
}
@Override
public void onRespondedToInvite(int response) {
}
@Override
public void onMessageSetUnread() {
}
@Override
public void onMessageNotExists() {
}
@Override
public void onLoadMessageStarted() {
}
@Override
public void onLoadMessageFinished() {
}
@Override
public void onLoadMessageError() {
}
@Override
public void onFetchAttachmentStarted(String attachmentName) {
}
@Override
public void onFetchAttachmentFinished() {
}
@Override
public void onFetchAttachmentError() {
}
@Override
public void onCalendarLinkClicked(long epochEventStartTime) {
}
});
fragment.openMessage(mMessageId);
startMessageOrderManager();
mOrderManager.moveTo(mMessageId);
updateNavigationArrows();
}
/**
* Disable/enable the previous/next buttons for the message view.
*/
private void updateNavigationArrows() {
mMoveToNewer.setEnabled((mOrderManager != null) && mOrderManager.canMoveToNewer());
mMoveToOlder.setEnabled((mOrderManager != null) && mOrderManager.canMoveToOlder());
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.moveToOlder:
moveToOlder();
break;
case R.id.moveToNewer:
moveToNewer();
break;
}
mMoveToNewerButton.setEnabled((mOrderManager != null) && mOrderManager.canMoveToNewer());
mMoveToOlderButton.setEnabled((mOrderManager != null) && mOrderManager.canMoveToOlder());
}
private boolean moveToOlder() {
if (mOrderManager != null && mOrderManager.moveToOlder()) {
mMessageId = mOrderManager.getCurrentMessageId();
mMessageViewFragment.openMessage(mMessageId);
if (mFragmentManager.isMessageSelected() && (mOrderManager != null)
&& mOrderManager.moveToOlder()) {
mFragmentManager.selectMessage(mOrderManager.getCurrentMessageId());
return true;
}
return false;
}
private boolean moveToNewer() {
if (mOrderManager != null && mOrderManager.moveToNewer()) {
mMessageId = mOrderManager.getCurrentMessageId();
mMessageViewFragment.openMessage(mMessageId);
if (mFragmentManager.isMessageSelected() && (mOrderManager != null)
&& mOrderManager.moveToNewer()) {
mFragmentManager.selectMessage(mOrderManager.getCurrentMessageId());
return true;
}
return false;
}
private class MailboxListFragmentCallback implements MailboxListFragment.Callback {
@Override
public void onRefresh(long accountId, long mailboxId) {
// Will be removed.
}
// TODO Rename to onSelectMailbox
@Override
public void onMailboxSelected(long accountId, long mailboxId) {
mFragmentManager.selectMailbox(mailboxId);
}
}
private class MessageListFragmentCallback implements MessageListFragment.Callback {
@Override
public void onSelectionChanged() {
// TODO Context mode
}
@Override
// TODO Rename to onSelectMessage
public void onMessageOpen(long messageId, long mailboxId) { // RENAME: OpenMessage ?
// TODO Deal with drafts. (Open MessageCompose instead.)
mFragmentManager.selectMessage(messageId);
}
@Override
public void onMailboxNotFound() { // RENAME: NotExists? (see MessageViewFragment)
// TODO: What to do??
}
}
private class MessageViewFragmentCallback implements MessageViewFragment.Callback {
@Override
public boolean onUrlInMessageClicked(String url) {
return false;
}
@Override
public void onRespondedToInvite(int response) {
}
@Override
public void onMessageSetUnread() {
}
@Override
public void onMessageNotExists() {
}
@Override
public void onLoadMessageStarted() {
}
@Override
public void onLoadMessageFinished() {
}
@Override
public void onLoadMessageError() {
}
@Override
public void onFetchAttachmentStarted(String attachmentName) {
}
@Override
public void onFetchAttachmentFinished() {
}
@Override
public void onFetchAttachmentError() {
}
@Override
public void onCalendarLinkClicked(long epochEventStartTime) {
}
}
@Override
public void onMessageViewFragmentShown(long accountId, long mailboxId, long messageId) {
mMessageViewButtonPanel.setVisibility(View.VISIBLE);
updateMessageOrderManager();
updateNavigationArrows();
}
@Override
public void onMessageViewFragmentHidden() {
mMessageViewButtonPanel.setVisibility(View.GONE);
stopMessageOrderManager();
}
/**
* STOPSHIP: Remove this.
* Rotate screen when the R key is pressed. Workaround for auto-orientation not working.

View File

@ -0,0 +1,383 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.util.Log;
import java.security.InvalidParameterException;
import java.util.ArrayList;
/*
TODO: When opening a mailbox I see this:
D Email : com.android.email.activity.MailboxListFragment openMailboxes
D Email : com.android.email.activity.MailboxListFragment onCreate *1 <- Why second instance???
D Email : com.android.email.activity.MailboxListFragment onActivityCreated
D Email : com.android.email.activity.MailboxListFragment onStart
D Email : com.android.email.activity.MailboxListFragment onResume
*/
/**
* A class manages what are showing on {@link MessageListXL} (i.e. account id, mailbox id, and
* message id), and show/hide fragments accordingly.
*
* TODO: Test it. It's testable if we implement MockFragmentTransaction, which may be too early
* to do so at this point. (API may not be stable enough yet.)
*
* TODO: See if the "restored fragments" hack can be removed if the fragments restore their
* state by themselves. (That'll require phone activity changes as well.)
*/
class MessageListXLFragmentManager {
private static final String BUNDLE_KEY_ACCOUNT_ID = "MessageListXl.state.account_id";
private static final String BUNDLE_KEY_MAILBOX_ID = "MessageListXl.state.mailbox_id";
private static final String BUNDLE_KEY_MESSAGE_ID = "MessageListXl.state.message_id";
/**
* List of fragments that are restored by the framework when the activity is being re-created.
* (e.g. for orientation change)
*/
private final ArrayList<Fragment> mRestoredFragments = new ArrayList<Fragment>();
private boolean mIsActivityStarted;
/** Current account id. (-1 = not selected) */
private long mAccountId = -1;
/** Current mailbox id. (-1 = not selected) */
private long mMailboxId = -1;
/** Current message id. (-1 = not selected) */
private long mMessageId = -1;
private MailboxListFragment mMailboxListFragment;
private MessageListFragment mMessageListFragment;
private MessageViewFragment mMessageViewFragment;
private MailboxListFragment.Callback mMailboxListFragmentCallback;
private MessageListFragment.Callback mMessageListFragmentCallback;
private MessageViewFragment.Callback mMessageViewFragmentCallback;
/**
* The interface that {@link MessageListXL} implements. We don't call its methods directly,
* in the hope that it'll make writing tests easier.
*/
public interface TargetActivity {
public FragmentTransaction openFragmentTransaction();
/**
* Called when MessageViewFragment is being shown.
* {@link MessageListXL} uses it to show the navigation buttons.
*/
public void onMessageViewFragmentShown(long accountId, long mailboxId, long messageId);
/**
* Called when MessageViewFragment is being hidden.
* {@link MessageListXL} uses it to hide the navigation buttons.
*/
public void onMessageViewFragmentHidden();
}
private final TargetActivity mTargetActivity;
public MessageListXLFragmentManager(TargetActivity targetActivity) {
mTargetActivity = targetActivity;
}
/** Set callback for fragment. */
public void setMailboxListFragmentCallback(
MailboxListFragment.Callback mailboxListFragmentCallback) {
mMailboxListFragmentCallback = mailboxListFragmentCallback;
}
/** Set callback for fragment. */
public void setMessageListFragmentCallback(
MessageListFragment.Callback messageListFragmentCallback) {
mMessageListFragmentCallback = messageListFragmentCallback;
}
/** Set callback for fragment. */
public void setMessageViewFragmentCallback(
MessageViewFragment.Callback messageViewFragmentCallback) {
mMessageViewFragmentCallback = messageViewFragmentCallback;
}
public long getAccountId() {
return mAccountId;
}
public long getMailboxId() {
return mMailboxId;
}
public long getMessageId() {
return mMessageId;
}
public boolean isAccountSelected() {
return getAccountId() != -1;
}
public boolean isMailboxSelected() {
return getMailboxId() != -1;
}
public boolean isMessageSelected() {
return getMessageId() != -1;
}
/**
* Called from {@link MessageListXL#onStart()}.
*
* When the activity is being started, we initialize the "restored" fragments.
*
* @see #initRestoredFragments
*/
public void setStart() {
if (mIsActivityStarted) {
return;
}
mIsActivityStarted = true;
initRestoredFragments();
}
/**
* Called from {@link MessageListXL#onStop()}.
*/
public void onStop() {
if (!mIsActivityStarted) {
return;
}
mIsActivityStarted = false;
}
public void onSaveInstanceState(Bundle outState) {
outState.putLong(BUNDLE_KEY_ACCOUNT_ID, mAccountId);
outState.putLong(BUNDLE_KEY_MAILBOX_ID, mMailboxId);
outState.putLong(BUNDLE_KEY_MESSAGE_ID, mMessageId);
}
public void loadState(Bundle savedInstanceState) {
mAccountId = savedInstanceState.getLong(BUNDLE_KEY_ACCOUNT_ID, -1);
mMailboxId = savedInstanceState.getLong(BUNDLE_KEY_MAILBOX_ID, -1);
mMessageId = savedInstanceState.getLong(BUNDLE_KEY_MESSAGE_ID, -1);
if (Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListXLFragmentManager: Restoring "
+ mAccountId + "," + mMailboxId + "," + mMessageId);
}
}
/**
* Called by {@link MessageListXL#onAttachFragment}.
*
* If the activity is not started yet, just store it in {@link #mRestoredFragments} to
* initialize it later.
*/
public void onAttachFragment(Fragment fragment) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListXLFragmentManager.onAttachFragment fragment=" +
fragment.getClass());
}
if (!mIsActivityStarted) {
mRestoredFragments.add(fragment);
return;
}
if (fragment instanceof MailboxListFragment) {
updateMailboxListFragment((MailboxListFragment) fragment);
} else if (fragment instanceof MessageListFragment) {
updateMessageListFragment((MessageListFragment) fragment);
} else if (fragment instanceof MessageViewFragment) {
updateMessageViewFragment((MessageViewFragment) fragment);
}
}
/**
* Called by {@link #setActivityStarted} to initialize the "restored" fragments.
*/
private void initRestoredFragments() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListXLFragmentManager.initRestoredFragments");
}
for (Fragment f : mRestoredFragments) {
onAttachFragment(f);
}
mRestoredFragments.clear();
}
/**
* Call it to select an account.
*/
public void selectAccount(long accountId) {
// TODO Handle "combined mailboxes".
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "selectAccount mAccountId=" + accountId);
}
if (accountId == -1) {
throw new InvalidParameterException();
}
if (mAccountId == accountId) {
return;
}
// Update members.
mAccountId = accountId;
mMailboxId = -1;
mMessageId = -1;
// Replace fragments if necessary.
final FragmentTransaction ft = mTargetActivity.openFragmentTransaction();
if (mMailboxListFragment == null) {
// The left pane not set yet.
// We can put it directly in the layout file, but then it'll have slightly different
// lifecycle as the other fragments. Let's create it here this way for now.
MailboxListFragment f = new MailboxListFragment();
ft.replace(R.id.left_pane, f);
}
if (mMessageListFragment != null) {
ft.remove(mMessageListFragment);
mMessageListFragment = null;
}
if (mMessageViewFragment != null) {
ft.remove(mMessageViewFragment);
mMessageViewFragment = null;
mTargetActivity.onMessageViewFragmentHidden(); // Don't forget to tell the activity.
}
ft.commit();
// If it's already shown, update it.
if (mMailboxListFragment != null) {
updateMailboxListFragment(mMailboxListFragment);
} else {
Log.w(Email.LOG_TAG, "MailboxListFragment not set yet.");
}
// TODO Open the inbox on the right pane.
}
private void updateMailboxListFragment(MailboxListFragment fragment) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "updateMailboxListFragment mAccountId=" + mAccountId);
}
if (mAccountId == -1) { // Shouldn't happen
throw new RuntimeException();
}
mMailboxListFragment = fragment;
fragment.setCallback(mMailboxListFragmentCallback);
fragment.openMailboxes(mAccountId);
}
/**
* Call it to select a mailbox.
*
* We assume the mailbox selected here belongs to the account selected with
* {@link #selectAccount}.
*/
public void selectMailbox(long mailboxId) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "selectMailbox mMailboxId=" + mailboxId);
}
if (mailboxId == -1) {
throw new InvalidParameterException();
}
if ((mMailboxId == mailboxId) && !isMessageSelected()) {
return;
}
// Update members.
mMailboxId = mailboxId;
mMessageId = -1;
// Update fragments.
if (mMessageListFragment == null) {
MessageListFragment f = new MessageListFragment();
mTargetActivity.openFragmentTransaction().replace(R.id.right_pane, f).commit();
if (mMessageViewFragment != null) {
// Message view will disappear.
mMessageViewFragment = null;
mTargetActivity.onMessageViewFragmentHidden(); // Don't forget to tell the activity.
}
} else {
updateMessageListFragment(mMessageListFragment);
}
}
private void updateMessageListFragment(MessageListFragment fragment) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "updateMessageListFragment mMailboxId=" + mMailboxId);
}
if (mAccountId == -1 || mMailboxId == -1) { // Shouldn't happen
throw new RuntimeException();
}
mMessageListFragment = fragment;
fragment.setCallback(mMessageListFragmentCallback);
fragment.openMailbox(mAccountId, mMailboxId);
}
/**
* Call it to select a mailbox.
*
* We assume the message passed here belongs to the account/mailbox selected with
* {@link #selectAccount} and {@link #selectMailbox}.
*/
public void selectMessage(long messageId) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "selectMessage messageId=" + messageId);
}
if (messageId == -1) {
throw new InvalidParameterException();
}
if (mMessageId == messageId) {
return;
}
// Update member.
mMessageId = messageId;
// Update fragments.
if (mMessageViewFragment == null) {
MessageViewFragment f = new MessageViewFragment();
// TODO We want to support message view -> [back] -> message list, but the back behavior
// with addToBackStack() is not too clear. We do it manually for now.
// See MessageListXL.onBackPressed().
mTargetActivity.openFragmentTransaction().replace(R.id.right_pane, f)
// .addToBackStack(null)
.commit();
mMessageListFragment = null;
} else {
updateMessageViewFragment(mMessageViewFragment);
}
}
private void updateMessageViewFragment(MessageViewFragment fragment) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "updateMessageViewFragment messageId=" + mMessageId);
}
if (mAccountId == -1 || mMailboxId == -1 || mMessageId == -1) { // Shouldn't happen
throw new RuntimeException();
}
mMessageViewFragment = fragment;
fragment.setCallback(mMessageViewFragmentCallback);
fragment.openMessage(mMessageId);
mTargetActivity.onMessageViewFragmentShown(getAccountId(), getMailboxId(), getMessageId());
}
}

View File

@ -265,7 +265,9 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
@Override
public void onCreate(Bundle savedInstanceState) {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageViewFragment onCreate");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageViewFragment onCreate");
}
super.onCreate(savedInstanceState);
mContext = getActivity().getApplicationContext();
@ -287,7 +289,9 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageViewFragment onCreateView");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageViewFragment onCreateView");
}
final View view = inflater.inflate(R.layout.message_view_fragment, container, false);
mSubjectView = (TextView) view.findViewById(R.id.subject);
@ -329,14 +333,18 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
@Override
public void onActivityCreated(Bundle savedInstanceState) {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageViewFragment onActivityCreated");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageViewFragment onActivityCreated");
}
super.onActivityCreated(savedInstanceState);
mController.addResultCallback(mControllerCallback);
}
@Override
public void onStart() {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageViewFragment onStart");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageViewFragment onStart");
}
super.onStart();
mStarted = true;
if (mMessageId != -1 || mFileEmailUri != null) {
@ -346,26 +354,34 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
@Override
public void onResume() {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageViewFragment onResume");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageViewFragment onResume");
}
super.onResume();
}
@Override
public void onPause() {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageViewFragment onPause");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageViewFragment onPause");
}
super.onPause();
}
@Override
public void onStop() {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageViewFragment onStop");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageViewFragment onStop");
}
mStarted = false;
super.onStop();
}
@Override
public void onDestroy() {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageViewFragment onDestroy");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageViewFragment onDestroy");
}
mController.removeResultCallback(mControllerCallback);
cancelAllTasks();
mMessageContentView.destroy();
@ -384,7 +400,9 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
@Override
public void onSaveInstanceState(Bundle outState) {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageViewFragment onSaveInstanceState");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageViewFragment onSaveInstanceState");
}
super.onSaveInstanceState(outState);
}
@ -421,7 +439,9 @@ public class MessageViewFragment extends Fragment implements View.OnClickListene
/** Called by activities to set an id of a message to open. */
public void openMessage(long messageId) {
if (Email.DEBUG) Log.d(Email.LOG_TAG, "MessageViewFragment openMessage");
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageViewFragment openMessage");
}
mFileEmailUri = null;
mMessageId = messageId;
mAccountId = -1;