649 lines
22 KiB
Java
649 lines
22 KiB
Java
/*
|
|
* Copyright (C) 2011 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 android.app.Activity;
|
|
import android.app.Fragment;
|
|
import android.app.FragmentTransaction;
|
|
import android.os.Bundle;
|
|
import android.util.Log;
|
|
import android.view.Menu;
|
|
import android.view.MenuInflater;
|
|
import android.view.MenuItem;
|
|
|
|
import com.android.email.Email;
|
|
import com.android.email.MessageListContext;
|
|
import com.android.email.R;
|
|
import com.android.emailcommon.Logging;
|
|
import com.android.emailcommon.provider.Account;
|
|
import com.android.emailcommon.provider.EmailContent.Message;
|
|
import com.android.emailcommon.provider.Mailbox;
|
|
|
|
import java.util.Set;
|
|
|
|
|
|
/**
|
|
* UI Controller for non x-large devices. Supports a single-pane layout.
|
|
*
|
|
* One one-pane, only at most one fragment can be installed at a time.
|
|
*
|
|
* Note: Always use {@link #commitFragmentTransaction} to operate fragment transactions,
|
|
* so that we can easily switch between synchronous and asynchronous transactions.
|
|
*
|
|
* Major TODOs
|
|
* - TODO Implement callbacks
|
|
*/
|
|
class UIControllerOnePane extends UIControllerBase {
|
|
private static final String BUNDLE_KEY_PREVIOUS_FRAGMENT
|
|
= "UIControllerOnePane.PREVIOUS_FRAGMENT";
|
|
|
|
// Our custom poor-man's back stack which has only one entry at maximum.
|
|
private Fragment mPreviousFragment;
|
|
|
|
// MailboxListFragment.Callback
|
|
@Override
|
|
public void onAccountSelected(long accountId) {
|
|
// It's from combined view, so "forceShowInbox" doesn't really matter.
|
|
// (We're always switching accounts.)
|
|
switchAccount(accountId, true);
|
|
}
|
|
|
|
// MailboxListFragment.Callback
|
|
@Override
|
|
public void onMailboxSelected(long accountId, long mailboxId, boolean nestedNavigation) {
|
|
if (nestedNavigation) {
|
|
return; // Nothing to do on 1-pane.
|
|
}
|
|
openMailbox(accountId, mailboxId);
|
|
}
|
|
|
|
// MailboxListFragment.Callback
|
|
@Override
|
|
public void onParentMailboxChanged() {
|
|
refreshActionBar();
|
|
}
|
|
|
|
// MessageListFragment.Callback
|
|
@Override
|
|
public void onAdvancingOpAccepted(Set<Long> affectedMessages) {
|
|
// Nothing to do on 1 pane.
|
|
}
|
|
|
|
// MessageListFragment.Callback
|
|
@Override
|
|
public void onEnterSelectionMode(boolean enter) {
|
|
// TODO Auto-generated method stub
|
|
}
|
|
|
|
// MessageListFragment.Callback
|
|
@Override
|
|
public void onListLoaded() {
|
|
// TODO Auto-generated method stub
|
|
}
|
|
|
|
// MessageListFragment.Callback
|
|
@Override
|
|
public void onMailboxNotFound() {
|
|
switchAccount(getUIAccountId(), true);
|
|
}
|
|
|
|
// MessageListFragment.Callback
|
|
@Override
|
|
public void onMessageOpen(
|
|
long messageId, long messageMailboxId, long listMailboxId, int type) {
|
|
if (type == MessageListFragment.Callback.TYPE_DRAFT) {
|
|
MessageCompose.actionEditDraft(mActivity, messageId);
|
|
} else {
|
|
open(mListContext, messageId);
|
|
}
|
|
}
|
|
|
|
// MessageListFragment.Callback
|
|
@Override
|
|
public boolean onDragStarted() {
|
|
// No drag&drop on 1-pane
|
|
return false;
|
|
}
|
|
|
|
// MessageListFragment.Callback
|
|
@Override
|
|
public void onDragEnded() {
|
|
// No drag&drop on 1-pane
|
|
}
|
|
|
|
// MessageViewFragment.Callback
|
|
@Override
|
|
public void onForward() {
|
|
MessageCompose.actionForward(mActivity, getMessageId());
|
|
}
|
|
|
|
// MessageViewFragment.Callback
|
|
@Override
|
|
public void onReply() {
|
|
MessageCompose.actionReply(mActivity, getMessageId(), false);
|
|
}
|
|
|
|
// MessageViewFragment.Callback
|
|
@Override
|
|
public void onReplyAll() {
|
|
MessageCompose.actionReply(mActivity, getMessageId(), true);
|
|
}
|
|
|
|
// MessageViewFragment.Callback
|
|
@Override
|
|
public void onCalendarLinkClicked(long epochEventStartTime) {
|
|
ActivityHelper.openCalendar(mActivity, epochEventStartTime);
|
|
}
|
|
|
|
// MessageViewFragment.Callback
|
|
@Override
|
|
public boolean onUrlInMessageClicked(String url) {
|
|
return ActivityHelper.openUrlInMessage(mActivity, url, getActualAccountId());
|
|
}
|
|
|
|
// MessageViewFragment.Callback
|
|
@Override
|
|
public void onLoadMessageError(String errorMessage) {
|
|
// TODO Auto-generated method stub
|
|
}
|
|
|
|
// MessageViewFragment.Callback
|
|
@Override
|
|
public void onLoadMessageFinished() {
|
|
// TODO Auto-generated method stub
|
|
}
|
|
|
|
// MessageViewFragment.Callback
|
|
@Override
|
|
public void onLoadMessageStarted() {
|
|
// TODO Auto-generated method stub
|
|
}
|
|
|
|
// This is all temporary as we'll have a different action bar controller for 1-pane.
|
|
private class ActionBarControllerCallback implements ActionBarController.Callback {
|
|
@Override
|
|
public int getTitleMode() {
|
|
if (isMailboxListInstalled()) {
|
|
return TITLE_MODE_ACCOUNT_WITH_ALL_FOLDERS_LABEL;
|
|
}
|
|
if (isMessageViewInstalled()) {
|
|
return TITLE_MODE_MESSAGE_SUBJECT;
|
|
}
|
|
return TITLE_MODE_ACCOUNT_WITH_MAILBOX;
|
|
}
|
|
|
|
public String getMessageSubject() {
|
|
if (isMessageViewInstalled() && getMessageViewFragment().isMessageOpen()) {
|
|
return getMessageViewFragment().getMessage().mSubject;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldShowUp() {
|
|
return isMessageViewInstalled()
|
|
|| (isMailboxListInstalled() && getMailboxListFragment().canNavigateUp());
|
|
}
|
|
|
|
@Override
|
|
public long getUIAccountId() {
|
|
return UIControllerOnePane.this.getUIAccountId();
|
|
}
|
|
|
|
@Override
|
|
public long getMailboxId() {
|
|
return UIControllerOnePane.this.getMailboxId();
|
|
}
|
|
|
|
@Override
|
|
public void onMailboxSelected(long accountId, long mailboxId) {
|
|
if (mailboxId == Mailbox.NO_MAILBOX) {
|
|
showAllMailboxes();
|
|
} else {
|
|
openMailbox(accountId, mailboxId);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isAccountSelected() {
|
|
return UIControllerOnePane.this.isAccountSelected();
|
|
}
|
|
|
|
@Override
|
|
public void onAccountSelected(long accountId) {
|
|
switchAccount(accountId, true); // Always go to inbox
|
|
}
|
|
|
|
@Override
|
|
public void onNoAccountsFound() {
|
|
Welcome.actionStart(mActivity);
|
|
mActivity.finish();
|
|
}
|
|
|
|
@Override
|
|
public String getSearchHint() {
|
|
if (!isMessageListInstalled()) {
|
|
return null;
|
|
}
|
|
return UIControllerOnePane.this.getSearchHint();
|
|
}
|
|
|
|
@Override
|
|
public void onSearchSubmit(String queryTerm) {
|
|
if (!isMessageListInstalled()) {
|
|
return;
|
|
}
|
|
UIControllerOnePane.this.onSearchSubmit(queryTerm);
|
|
}
|
|
|
|
@Override
|
|
public void onSearchExit() {
|
|
UIControllerOnePane.this.onSearchExit();
|
|
}
|
|
}
|
|
|
|
public UIControllerOnePane(EmailActivity activity) {
|
|
super(activity);
|
|
}
|
|
|
|
@Override
|
|
protected ActionBarController createActionBarController(Activity activity) {
|
|
|
|
// For now, we just reuse the same action bar controller used for 2-pane.
|
|
// We may change it later.
|
|
|
|
return new ActionBarController(activity, activity.getLoaderManager(),
|
|
activity.getActionBar(), new ActionBarControllerCallback());
|
|
}
|
|
|
|
@Override
|
|
public void onSaveInstanceState(Bundle outState) {
|
|
super.onSaveInstanceState(outState);
|
|
if (mPreviousFragment != null) {
|
|
mFragmentManager.putFragment(outState,
|
|
BUNDLE_KEY_PREVIOUS_FRAGMENT, mPreviousFragment);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onRestoreInstanceState(Bundle savedInstanceState) {
|
|
super.onRestoreInstanceState(savedInstanceState);
|
|
mPreviousFragment = mFragmentManager.getFragment(savedInstanceState,
|
|
BUNDLE_KEY_PREVIOUS_FRAGMENT);
|
|
}
|
|
|
|
@Override
|
|
public int getLayoutId() {
|
|
return R.layout.email_activity_one_pane;
|
|
}
|
|
|
|
@Override
|
|
public long getUIAccountId() {
|
|
if (mListContext != null) {
|
|
return mListContext.mAccountId;
|
|
}
|
|
if (isMailboxListInstalled()) {
|
|
return getMailboxListFragment().getAccountId();
|
|
}
|
|
return Account.NO_ACCOUNT;
|
|
}
|
|
|
|
private long getMailboxId() {
|
|
if (mListContext != null) {
|
|
return mListContext.getMailboxId();
|
|
}
|
|
return Mailbox.NO_MAILBOX;
|
|
}
|
|
|
|
@Override
|
|
public boolean onBackPressed(boolean isSystemBackKey) {
|
|
if (Email.DEBUG) {
|
|
// This is VERY important -- no check for DEBUG_LIFECYCLE
|
|
Log.d(Logging.LOG_TAG, this + " onBackPressed: " + isSystemBackKey);
|
|
}
|
|
// The action bar controller has precedence. Must call it first.
|
|
if (mActionBarController.onBackPressed(isSystemBackKey)) {
|
|
return true;
|
|
}
|
|
// If the mailbox list is shown and showing a nested mailbox, let it navigate up first.
|
|
if (isMailboxListInstalled() && getMailboxListFragment().navigateUp()) {
|
|
if (DEBUG_FRAGMENTS) {
|
|
Log.d(Logging.LOG_TAG, this + " Back: back handled by mailbox list");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Custom back stack
|
|
if (shouldPopFromBackStack(isSystemBackKey)) {
|
|
if (DEBUG_FRAGMENTS) {
|
|
Log.d(Logging.LOG_TAG, this + " Back: Popping from back stack");
|
|
}
|
|
popFromBackStack();
|
|
return true;
|
|
}
|
|
|
|
// No entry in the back stack.
|
|
// If the message view is shown, show the "parent" message list.
|
|
// This happens when we get a deep link to a message. (e.g. from a widget)
|
|
if (isMessageViewInstalled()) {
|
|
if (DEBUG_FRAGMENTS) {
|
|
Log.d(Logging.LOG_TAG, this + " Back: Message view -> Message List");
|
|
}
|
|
openMailbox(mListContext.mAccountId, mListContext.getMailboxId());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void openInternal(final MessageListContext listContext, final long messageId) {
|
|
if (Email.DEBUG) {
|
|
// This is VERY important -- don't check for DEBUG_LIFECYCLE
|
|
Log.i(Logging.LOG_TAG, this + " open " + listContext + " messageId=" + messageId);
|
|
}
|
|
|
|
if (messageId != Message.NO_MESSAGE) {
|
|
openMessage(messageId);
|
|
} else {
|
|
showFragment(MessageListFragment.newInstance(listContext));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return currently installed {@link Fragment} (1-pane has only one at most), or null if none
|
|
* exists.
|
|
*/
|
|
private Fragment getInstalledFragment() {
|
|
if (isMailboxListInstalled()) {
|
|
return getMailboxListFragment();
|
|
} else if (isMessageListInstalled()) {
|
|
return getMessageListFragment();
|
|
} else if (isMessageViewInstalled()) {
|
|
return getMessageViewFragment();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Show the mailbox list.
|
|
*
|
|
* This is the only way to open the mailbox list on 1-pane.
|
|
* {@link #open(MessageListContext, long)} will only open either the message list or the
|
|
* message view.
|
|
*/
|
|
private void openMailboxList(long accountId) {
|
|
setListContext(null);
|
|
showFragment(MailboxListFragment.newInstance(accountId, Mailbox.NO_MAILBOX, false));
|
|
}
|
|
|
|
private void openMessage(long messageId) {
|
|
showFragment(MessageViewFragment.newInstance(messageId));
|
|
}
|
|
|
|
/**
|
|
* Use this instead of {@link FragmentTransaction#commit}. We may switch to the asynchronous
|
|
* transaction some day.
|
|
*/
|
|
private void commitFragmentTransaction(FragmentTransaction ft) {
|
|
if (!ft.isEmpty()) {
|
|
// STOPSHIP Don't use AllowingStateLoss. See b/4519430
|
|
ft.commitAllowingStateLoss();
|
|
mFragmentManager.executePendingTransactions();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Push the installed fragment into our custom back stack (or optionally
|
|
* {@link FragmentTransaction#remove} it) and {@link FragmentTransaction#add} {@code fragment}.
|
|
*
|
|
* @param fragment {@link Fragment} to be added.
|
|
*
|
|
* TODO Delay-call the whole method and use the synchronous transaction.
|
|
*/
|
|
private void showFragment(Fragment fragment) {
|
|
final FragmentTransaction ft = mFragmentManager.beginTransaction();
|
|
final Fragment installed = getInstalledFragment();
|
|
if ((installed instanceof MessageViewFragment)
|
|
&& (fragment instanceof MessageViewFragment)) {
|
|
// Newer/older navigation, auto-advance, etc.
|
|
// In this case we want to keep the backstack untouched, so that after back navigation
|
|
// we can restore the message list, including scroll position and batch selection.
|
|
} else {
|
|
if (DEBUG_FRAGMENTS) {
|
|
Log.i(Logging.LOG_TAG, this + " backstack: [push] " + getInstalledFragment()
|
|
+ " -> " + fragment);
|
|
}
|
|
if (mPreviousFragment != null) {
|
|
if (DEBUG_FRAGMENTS) {
|
|
Log.d(Logging.LOG_TAG, this + " showFragment: destroying previous fragment "
|
|
+ mPreviousFragment);
|
|
}
|
|
removeFragment(ft, mPreviousFragment);
|
|
mPreviousFragment = null;
|
|
}
|
|
// Remove the current fragment or push it into the backstack.
|
|
if (installed != null) {
|
|
if (installed instanceof MessageViewFragment) {
|
|
// Message view should never be pushed to the backstack.
|
|
if (DEBUG_FRAGMENTS) {
|
|
Log.d(Logging.LOG_TAG, this + " showFragment: removing " + installed);
|
|
}
|
|
ft.remove(installed);
|
|
} else {
|
|
// Other fragments should be pushed.
|
|
mPreviousFragment = installed;
|
|
if (DEBUG_FRAGMENTS) {
|
|
Log.d(Logging.LOG_TAG, this + " showFragment: detaching "
|
|
+ mPreviousFragment);
|
|
}
|
|
ft.detach(mPreviousFragment);
|
|
}
|
|
}
|
|
}
|
|
// Show the new one
|
|
if (DEBUG_FRAGMENTS) {
|
|
Log.d(Logging.LOG_TAG, this + " showFragment: replacing with " + fragment);
|
|
}
|
|
ft.replace(R.id.fragment_placeholder, fragment);
|
|
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
|
|
commitFragmentTransaction(ft);
|
|
}
|
|
|
|
/**
|
|
* @param isSystemBackKey <code>true</code> if the system back key was pressed.
|
|
* <code>false</code> if it's caused by the "home" icon click on the action bar.
|
|
* @return true if we should pop from our custom back stack.
|
|
*/
|
|
private boolean shouldPopFromBackStack(boolean isSystemBackKey) {
|
|
if (mPreviousFragment == null) {
|
|
return false; // Nothing in the back stack
|
|
}
|
|
if (mPreviousFragment instanceof MessageViewFragment) {
|
|
throw new IllegalStateException("Message view should never be in backstack");
|
|
}
|
|
final Fragment installed = getInstalledFragment();
|
|
if (installed == null) {
|
|
// If no fragment is installed right now, do nothing.
|
|
return false;
|
|
}
|
|
|
|
// Okay now we have 2 fragments; the one in the back stack and the one that's currently
|
|
// installed.
|
|
if (mPreviousFragment.getClass() == installed.getClass()) {
|
|
// We never want to go back to the same kind of fragment, which happens when the user
|
|
// is on the message list, and selects another mailbox on the action bar.
|
|
return false;
|
|
}
|
|
|
|
if (isSystemBackKey) {
|
|
// In other cases, the system back key should always work.
|
|
return true;
|
|
} else {
|
|
// Home icon press -- there are cases where we don't want it to work.
|
|
|
|
// Disallow the Message list <-> mailbox list transition
|
|
if ((mPreviousFragment instanceof MailboxListFragment)
|
|
&& (installed instanceof MessageListFragment)) {
|
|
return false;
|
|
}
|
|
if ((mPreviousFragment instanceof MessageListFragment)
|
|
&& (installed instanceof MailboxListFragment)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pop from our custom back stack.
|
|
*
|
|
* TODO Delay-call the whole method and use the synchronous transaction.
|
|
*/
|
|
private void popFromBackStack() {
|
|
if (mPreviousFragment == null) {
|
|
return;
|
|
}
|
|
final FragmentTransaction ft = mFragmentManager.beginTransaction();
|
|
final Fragment installed = getInstalledFragment();
|
|
if (DEBUG_FRAGMENTS) {
|
|
Log.i(Logging.LOG_TAG, this + " backstack: [pop] " + installed + " -> "
|
|
+ mPreviousFragment);
|
|
}
|
|
removeFragment(ft, installed);
|
|
|
|
// Restore listContext.
|
|
if (mPreviousFragment instanceof MailboxListFragment) {
|
|
setListContext(null);
|
|
} else if (mPreviousFragment instanceof MessageListFragment) {
|
|
setListContext(((MessageListFragment) mPreviousFragment).getListContext());
|
|
} else {
|
|
throw new IllegalStateException("Message view should never be in backstack");
|
|
}
|
|
|
|
ft.attach(mPreviousFragment);
|
|
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
|
|
mPreviousFragment = null;
|
|
commitFragmentTransaction(ft);
|
|
return;
|
|
}
|
|
|
|
private void showAllMailboxes() {
|
|
if (!isAccountSelected()) {
|
|
return; // Can happen because of asynchronous fragment transactions.
|
|
}
|
|
|
|
openMailboxList(getUIAccountId());
|
|
}
|
|
|
|
@Override
|
|
protected void installMailboxListFragment(MailboxListFragment fragment) {
|
|
stopMessageOrderManager();
|
|
super.installMailboxListFragment(fragment);
|
|
}
|
|
|
|
@Override
|
|
protected void installMessageListFragment(MessageListFragment fragment) {
|
|
stopMessageOrderManager();
|
|
super.installMessageListFragment(fragment);
|
|
}
|
|
|
|
@Override
|
|
protected long getMailboxSettingsMailboxId() {
|
|
return isMessageListInstalled()
|
|
? getMessageListFragment().getMailboxId()
|
|
: Mailbox.NO_MAILBOX;
|
|
}
|
|
|
|
@Override
|
|
public boolean onPrepareOptionsMenu(MenuInflater inflater, Menu menu) {
|
|
// First, let the base class do what it has to do.
|
|
super.onPrepareOptionsMenu(inflater, menu);
|
|
|
|
// Then override
|
|
final boolean messageViewVisible = isMessageViewInstalled();
|
|
if (messageViewVisible) {
|
|
menu.findItem(R.id.search).setVisible(false);
|
|
menu.findItem(R.id.compose).setVisible(false);
|
|
menu.findItem(R.id.refresh).setVisible(false);
|
|
menu.findItem(R.id.account_settings).setVisible(false);
|
|
menu.findItem(R.id.mailbox_settings).setVisible(false);
|
|
|
|
final MessageOrderManager om = getMessageOrderManager();
|
|
menu.findItem(R.id.newer).setVisible(true);
|
|
menu.findItem(R.id.older).setVisible(true);
|
|
// orderManager shouldn't be null when the message view is installed, but just in case..
|
|
menu.findItem(R.id.newer).setEnabled((om != null) && om.canMoveToNewer());
|
|
menu.findItem(R.id.older).setEnabled((om != null) && om.canMoveToOlder());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
switch (item.getItemId()) {
|
|
case R.id.newer:
|
|
moveToNewer();
|
|
return true;
|
|
case R.id.older:
|
|
moveToOlder();
|
|
return true;
|
|
}
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
|
|
@Override
|
|
protected boolean isRefreshEnabled() {
|
|
// Refreshable only when an actual account is selected, and message view isn't shown.
|
|
// (i.e. only available on the mailbox list or the message view, but not on the combined
|
|
// one)
|
|
return isActualAccountSelected() && !isMessageViewInstalled();
|
|
}
|
|
|
|
@Override
|
|
protected void onRefresh() {
|
|
if (!isRefreshEnabled()) {
|
|
return;
|
|
}
|
|
if (isMessageListInstalled()) {
|
|
mRefreshManager.refreshMessageList(getActualAccountId(), getMailboxId(), true);
|
|
} else {
|
|
mRefreshManager.refreshMailboxList(getActualAccountId());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected boolean isRefreshInProgress() {
|
|
if (!isRefreshEnabled()) {
|
|
return false;
|
|
}
|
|
if (isMessageListInstalled()) {
|
|
return mRefreshManager.isMessageListRefreshing(getMailboxId());
|
|
} else {
|
|
return mRefreshManager.isMailboxListRefreshing(getActualAccountId());
|
|
}
|
|
}
|
|
|
|
@Override protected void navigateToMessage(long messageId) {
|
|
openMessage(messageId);
|
|
}
|
|
|
|
@Override protected void updateNavigationArrows() {
|
|
refreshActionBar();
|
|
}
|
|
}
|