/* * 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 com.android.email.UiUtilities; import com.android.emailcommon.Logging; import com.android.emailcommon.provider.EmailContent.Account; import com.android.emailcommon.provider.EmailContent.Mailbox; import android.app.ActionBar; import android.app.FragmentManager; import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.security.InvalidParameterException; /** * A class manages what are showing on {@link MessageListXL} (i.e. account id, mailbox id, and * message id), and show/hide fragments accordingly. * * TODO Highlight selected message on message list * * 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.) */ 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"; private static final String BUNDLE_KEY_MESSAGE_LIST_STATE = "MessageListXl.state.message_list_state"; private final Context mContext; private boolean mIsActivityResumed; /** 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 ActionBar mActionBar; private ThreePaneLayout mThreePane; private View mActionBarMailboxNameView; private TextView mActionBarMailboxName; private TextView mActionBarUnreadCount; private MailboxListFragment mMailboxListFragment; private MessageListFragment mMessageListFragment; private MessageViewFragment mMessageViewFragment; private MessageCommandButtonView mMessageCommandButtons; private MailboxFinder mMailboxFinder; private final MailboxFinderCallback mMailboxFinderCallback = new MailboxFinderCallback(); private final ThreePaneLayoutCallback mThreePaneLayoutCallback = new ThreePaneLayoutCallback(); /** * Save state for the "message list -> message view -[back press]-> message list" transition. */ private MessageListFragment.State mMessageListFragmentState; /** * The interface that {@link MessageListXL} implements. We don't call its methods directly, * in the hope that it'll make writing tests easier, and make it clear which methods are needed * for MessageListXLFragmentManager. */ public interface TargetActivity { public ActionBar getActionBar(); public FragmentManager getFragmentManager(); /** * Called when the selected account is on security-hold. */ public void onAccountSecurityHold(long accountId); /** * Called when the current account has changed. */ public void onAccountChanged(long accountId); /** * Called when the current mailbox has changed. */ public void onMailboxChanged(long accountId, long newMailboxId); /** Called when "move to newer" button is pressed. */ public void onMoveToNewer(); /** Called when "move to older" button is pressed. */ public void onMoveToOlder(); public View findViewById(int id); } private final TargetActivity mTargetActivity; public MessageListXLFragmentManager(MessageListXL activity) { mContext = activity; mTargetActivity = activity; } /** * Must be called just after the activity sets up the content view. * * (Due to the complexity regarding class/activity initialization order, we can't do this in * the constructor.) */ public void onActivityViewReady() { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, "MessageListXLFragmentManager.onActivityViewReady"); } mThreePane = (ThreePaneLayout) mTargetActivity.findViewById(R.id.three_pane); mThreePane.setCallback(mThreePaneLayoutCallback); FragmentManager fm = mTargetActivity.getFragmentManager(); mMailboxListFragment = (MailboxListFragment) fm.findFragmentById( mThreePane.getLeftPaneId()); mMessageListFragment = (MessageListFragment) fm.findFragmentById( mThreePane.getMiddlePaneId()); mMessageViewFragment = (MessageViewFragment) fm.findFragmentById( mThreePane.getRightPaneId()); mMessageCommandButtons = mThreePane.getMessageCommandButtons(); mMessageCommandButtons.setCallback(new CommandButtonCallback()); mActionBar = mTargetActivity.getActionBar(); // Set a view for the current mailbox to the action bar. final LayoutInflater inflater = LayoutInflater.from(mContext); mActionBarMailboxNameView = inflater.inflate(R.layout.action_bar_current_mailbox, null); mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, ActionBar.DISPLAY_SHOW_CUSTOM); final ActionBar.LayoutParams customViewLayout = new ActionBar.LayoutParams( ActionBar.LayoutParams.WRAP_CONTENT, ActionBar.LayoutParams.MATCH_PARENT); customViewLayout.setMargins(mContext.getResources().getDimensionPixelSize( R.dimen.action_bar_mailbox_name_left_margin) , 0, 0, 0); mActionBar.setCustomView(mActionBarMailboxNameView, customViewLayout); mActionBarMailboxName = (TextView) mActionBarMailboxNameView.findViewById(R.id.mailbox_name); mActionBarUnreadCount = (TextView) mActionBarMailboxNameView.findViewById(R.id.unread_count); } /** Set callback for fragment. */ public void setMailboxListFragmentCallback( MailboxListFragment.Callback mailboxListFragmentCallback) { mMailboxListFragment.setCallback(mailboxListFragmentCallback); } /** Set callback for fragment. */ public void setMessageListFragmentCallback( MessageListFragment.Callback messageListFragmentCallback) { mMessageListFragment.setCallback(messageListFragmentCallback); } /** Set callback for fragment. */ public void setMessageViewFragmentCallback( MessageViewFragment.Callback messageViewFragmentCallback) { mMessageViewFragment.setCallback(messageViewFragmentCallback); } /** * @return the currently selected account ID, *or* {@link Account#ACCOUNT_ID_COMBINED_VIEW}. * * @see #getActualAccountId() */ public long getUIAccountId() { return mAccountId; } /** * @return the currently selected account ID. If the current view is the combined view, * it'll return -1. * * @see #getUIAccountId() */ public long getActualAccountId() { return mAccountId == Account.ACCOUNT_ID_COMBINED_VIEW ? -1 : mAccountId; } public long getMailboxId() { return mMailboxId; } public long getMessageId() { return mMessageId; } /** * @return true if an account is selected, or the current view is the combined view. */ public boolean isAccountSelected() { return getUIAccountId() != -1; } public boolean isMailboxSelected() { return getMailboxId() != -1; } public boolean isMessageSelected() { return getMessageId() != -1; } public MailboxListFragment getMailboxListFragment() { return mMailboxListFragment; } public MessageListFragment getMessageListFragment() { return mMessageListFragment; } public MessageViewFragment getMessageViewFragment() { return mMessageViewFragment; } /** * Called from {@link MessageListXL#onStart}. */ public void onStart() { // Nothing to do } /** * Called from {@link MessageListXL#onResume}. */ public void onResume() { if (mIsActivityResumed) { return; } mIsActivityResumed = true; updateActionBar(); } /** * Called from {@link MessageListXL#onPause}. */ public void onPause() { if (!mIsActivityResumed) { return; } mIsActivityResumed = false; saveMessageListFragmentState(); } /** * Called from {@link MessageListXL#onStop}. */ public void onStop() { // Nothing to do } /** * Called from {@link MessageListXL#onDestroy}. */ public void onDestroy() { closeMailboxFinder(); } 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); outState.putParcelable(BUNDLE_KEY_MESSAGE_LIST_STATE, mMessageListFragmentState); } public void loadState(Bundle savedInstanceState) { long accountId = savedInstanceState.getLong(BUNDLE_KEY_ACCOUNT_ID, -1); long mailboxId = savedInstanceState.getLong(BUNDLE_KEY_MAILBOX_ID, -1); long messageId = savedInstanceState.getLong(BUNDLE_KEY_MESSAGE_ID, -1); mMessageListFragmentState = savedInstanceState.getParcelable(BUNDLE_KEY_MESSAGE_LIST_STATE); if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, "MessageListXLFragmentManager: Restoring " + accountId + "," + mailboxId + "," + messageId); } if (accountId == -1) { return; } // selectAccount() calls selectMailbox/Message() if necessary. selectAccount(accountId, mailboxId, messageId); } private void saveMessageListFragmentState() { if (mMessageListFragment != null) { mMessageListFragmentState = mMessageListFragment.getState(); } } private void restoreMesasgeListState() { if ((mMessageListFragment != null) && (mMessageListFragmentState != null)) { mMessageListFragmentState.restore(mMessageListFragment); mMessageListFragmentState = null; } } private void updateActionBar() { // If the left pane (mailbox list pane) is hidden, the back action on action bar will be // enabled, and we also show the current mailbox name. final int visiblePanes = mThreePane.getVisiblePanes(); final boolean leftPaneHidden = ((visiblePanes & ThreePaneLayout.PANE_LEFT) == 0); mActionBar.setDisplayOptions(leftPaneHidden ? ActionBar.DISPLAY_HOME_AS_UP : 0, ActionBar.DISPLAY_HOME_AS_UP); mActionBarMailboxNameView.setVisibility(leftPaneHidden ? View.VISIBLE : View.GONE); } public void setCurrentMailboxName(String mailboxName, int unreadCount) { mActionBarMailboxName.setText(mailboxName); // Note on action bar, we show only "unread count". Some mailboxes such as Outbox don't // have the idea of "unread count", in which case we just omit the count. mActionBarUnreadCount.setText( UiUtilities.getMessageCountForUi(mContext, unreadCount, true)); } /** * Call it to select an account. * * @param accountId account ID. Must not be -1. * @param mailboxId mailbox ID. Pass -1 to open account's inbox. * @param messageId message ID. Pass -1 to not open a message. */ public void selectAccount(long accountId, long mailboxId, long messageId) { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, "selectAccount mAccountId=" + accountId); } if (accountId == -1) { throw new InvalidParameterException(); } if (mAccountId == accountId) { return; } // Update members. mAccountId = accountId; mMailboxId = -1; mMessageId = -1; // In case of "message list -> message view -> change account", we don't have to keep it. mMessageListFragmentState = null; // Open mailbox list, clear message list / message view mMailboxListFragment.openMailboxes(mAccountId); mMessageListFragment.clearContent(); mThreePane.showLeftPane(); // Show mailbox list if ((accountId == Account.ACCOUNT_ID_COMBINED_VIEW) && (mailboxId == -1)) { // When opening the Combined view, the right pane will be "combined inbox". selectMailbox(Mailbox.QUERY_ALL_INBOXES, -1); } else if (mailboxId == -1) { startInboxLookup(); } else { selectMailbox(mailboxId, messageId); } mTargetActivity.onAccountChanged(mAccountId); } /** * Handles the back event. * * @param isSystemBackKey See {@link ThreePaneLayout#onBackPressed} * @return true if the event is handled. */ public boolean onBackPressed(boolean isSystemBackKey) { return mThreePane.onBackPressed(isSystemBackKey); } /** * If the current view is MessageView, go back to MessageList. */ public void goBackToMailbox() { if (isMessageSelected()) { mThreePane.showLeftPane(); // Show mailbox list } } /** * Call it to select a mailbox. * * We assume the mailbox selected here belongs to the account selected with * {@link #selectAccount}. * * @param mailboxId ID of mailbox * @param messageId message ID. Pass -1 to not open a message. */ public void selectMailbox(long mailboxId, long messageId) { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, "selectMailbox mMailboxId=" + mailboxId); } if (mailboxId == -1) { throw new InvalidParameterException(); } if (mMailboxId == mailboxId) { return; } // Update members. mMailboxId = mailboxId; mMessageId = -1; // Open mailbox mMessageListFragment.openMailbox(mMailboxId); restoreMesasgeListState(); mMailboxListFragment.setSelectedMailbox(mMailboxId); mTargetActivity.onMailboxChanged(mAccountId, mMailboxId); if (messageId == -1) { mThreePane.showLeftPane(); // Show mailbox list } else { selectMessage(messageId); } } /** * 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(Logging.LOG_TAG, "selectMessage messageId=" + messageId); } if (messageId == -1) { throw new InvalidParameterException(); } if (mMessageId == messageId) { return; } saveMessageListFragmentState(); // Update member. mMessageId = messageId; // Open message mMessageListFragment.setSelectedMessage(mMessageId); mMessageViewFragment.openMessage(mMessageId); mThreePane.showRightPane(); // Show message view } /** * Unselect the currently viewed message, if any, and release the resoruce grabbed by the * message view. * * This must be called when the three pane reports that the message view pane gets hidden. */ private void onMessageViewClosed() { mMessageId = -1; mMessageListFragment.setSelectedMessage(-1); mMessageViewFragment.clearContent(); } private void startInboxLookup() { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, "startLookForInbox account=" + mAccountId); } closeMailboxFinder(); mMailboxFinder = new MailboxFinder(mContext, mAccountId, Mailbox.TYPE_INBOX, mMailboxFinderCallback); mMailboxFinder.startLookup(); } private void closeMailboxFinder() { if (mMailboxFinder != null) { mMailboxFinder.cancel(); mMailboxFinder = null; } } private class MailboxFinderCallback implements MailboxFinder.Callback { @Override public void onAccountNotFound() { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, "MailboxFinderCallback.onAccountNotFound"); } // Shouldn't happen } @Override public void onAccountSecurityHold(long accountId) { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, "MailboxFinderCallback.onAccountSecurityHold"); } mTargetActivity.onAccountSecurityHold(accountId); } @Override public void onMailboxFound(long accountId, long mailboxId) { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, " Found inbox"); } selectMailbox(mailboxId, -1); } @Override public void onMailboxNotFound(long accountId) { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, "MailboxFinderCallback.onMailboxNotFound"); } // Shouldn't happen } } public void updateMessageCommandButtons(boolean enableMoveToNewer, boolean enableMoveToOlder, int currentPosition, int countMessages) { mMessageCommandButtons.enableNavigationButtons(enableMoveToNewer, enableMoveToOlder, currentPosition, countMessages); } private class CommandButtonCallback implements MessageCommandButtonView.Callback { @Override public void onMoveToNewer() { mTargetActivity.onMoveToNewer(); } @Override public void onMoveToOlder() { mTargetActivity.onMoveToOlder(); } } private class ThreePaneLayoutCallback implements ThreePaneLayout.Callback { @Override public void onVisiblePanesChanged(int previousVisiblePanes) { updateActionBar(); final int visiblePanes = mThreePane.getVisiblePanes(); if (((visiblePanes & ThreePaneLayout.PANE_RIGHT) == 0) && ((previousVisiblePanes & ThreePaneLayout.PANE_RIGHT) != 0)) { // Message view just got hidden onMessageViewClosed(); } mMessageListFragment.setVisibility((visiblePanes & ThreePaneLayout.PANE_MIDDLE) != 0); } } }