Merge "Implement 1-pane navigation."

This commit is contained in:
Makoto Onuki 2011-06-03 14:16:49 -07:00 committed by Android (Google) Code Review
commit 03b863d68e
12 changed files with 574 additions and 440 deletions

View File

@ -39,6 +39,17 @@
android:icon="@drawable/ic_menu_refresh_holo_light"
android:showAsAction="ifRoom"
/>
<!--
STOPSHIP "Show all folders" should be in the account spinner, not here.
It's temporary UI until we get the ICS style account spinner.
-->
<item
android:id="@+id/show_all_folders"
android:orderInCategory="400"
android:title="@string/mailbox_list_account_selector_show_all_folders"
android:showAsAction="ifRoom"
android:visible="false"
/>
<item
android:id="@+id/account_settings"
android:orderInCategory="2000"

View File

@ -272,6 +272,9 @@
<!-- Label shown in the account selector to select "Combined view", which contains
Combined Inbox, Combined Outbox, etc. [CHAR LIMIT=30] -->
<string name="mailbox_list_account_selector_combined_view">Combined view</string>
<!-- Label shown in the account/mailbox selector to switch to the show all the top-level
mailboxes. [CHAR LIMIT=30] -->
<string name="mailbox_list_account_selector_show_all_folders">Show all folders</string>
<!-- Appears at the bottom of list of messages; user selects to load more messages from that folder. -->
<string name="message_list_load_more_messages_action">Load more messages</string>

View File

@ -58,7 +58,7 @@ import java.util.ArrayList;
* Because this activity is device agnostic, so most of the UI aren't owned by this, but by
* the UIController.
*/
public class EmailActivity extends Activity implements View.OnClickListener {
public class EmailActivity extends Activity implements View.OnClickListener, FragmentInstallable {
private static final String EXTRA_ACCOUNT_ID = "ACCOUNT_ID";
private static final String EXTRA_MAILBOX_ID = "MAILBOX_ID";
private static final String EXTRA_MESSAGE_ID = "MESSAGE_ID";
@ -196,9 +196,6 @@ public class EmailActivity extends Activity implements View.OnClickListener {
int errorBannerHeight = getResources().getDimensionPixelSize(R.dimen.error_message_height);
mErrorBanner = new BannerController(this, errorMessage, errorBannerHeight);
// Install restored fragments.
mUIController.installRestoredFragments();
if (savedInstanceState != null) {
mUIController.restoreInstanceState(savedInstanceState);
} else {
@ -233,12 +230,11 @@ public class EmailActivity extends Activity implements View.OnClickListener {
}
@Override
public void onAttachFragment(Fragment fragment) {
public void onInstallFragment(Fragment fragment) {
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, this + " onAttachFragment fragment=" + fragment);
Log.d(Logging.LOG_TAG, this + " onInstallFragment fragment=" + fragment);
}
super.onAttachFragment(fragment);
mUIController.onAttachFragment(fragment);
mUIController.onInstallFragment(fragment);
}
@Override

View File

@ -0,0 +1,36 @@
/*
* 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;
/**
* Interface for {@link Activity} that can "install" fragments.
*/
public interface FragmentInstallable {
/**
* Called when a {@link Fragment} wants to be installed to the host activity.
*
* Fragments which use this MUST call this from {@link Fragment#onActivityCreated} using
* {@link UiUtilities#installFragment}.
*
* This means a host {@link Activity} can safely assume a passed {@link Fragment} is already
* created.
*/
public void onInstallFragment(Fragment fragment);
}

View File

@ -463,6 +463,8 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
lv.setOnDragListener(this);
startLoading(mParentMailboxId, mHighlightedMailboxId);
UiUtilities.installFragment(this);
}
public void setCallback(Callback callback) {

View File

@ -394,6 +394,8 @@ public class MessageListFragment extends ListFragment
}
startLoading();
UiUtilities.installFragment(this);
}
@Override

View File

@ -47,6 +47,8 @@ import android.widget.ImageView;
public class MessageViewFragment extends MessageViewFragmentBase
implements CheckBox.OnCheckedChangeListener, MoveMessageToDialog.Callback {
/** Argument name(s) */
private static final String ARG_OPENER_ACCOUNT_ID = "accountId";
private static final String ARG_OPENER_MAILBOX_ID = "mailboxId";
private static final String ARG_MESSAGE_ID = "messageId";
private ImageView mFavoriteIcon;
@ -125,13 +127,33 @@ public class MessageViewFragment extends MessageViewFragmentBase
* Create a new instance with initialization parameters.
*
* This fragment should be created only with this method. (Arguments should always be set.)
*
* @param openerAccountId account ID that's used in the UI that opened this fragment.
* The primary use is for the back navigation to determine which mailbox to show.
*
* Note this is not necessarily the same ID as the actual account ID for the message.
* If a message is opened on the combined view, the caller probably want to pass
* {@link Account#ACCOUNT_ID_COMBINED_VIEW} so that back will navigate to the
* combined view.
*
* @param openerMailboxId mailbox ID that's used in the UI that opened this fragment.
* The primary use is for the back navigation to determine which mailbox to show.
*
* Note this is not necessarily the same ID as the actual mailbox ID for the message.
* If a message is opened on the combined view, the caller probably want to pass
* a combined mailbox ID so that back will navigate to it.
*
* @param messageId ID of the message to open
*/
public static MessageViewFragment newInstance(long messageId) {
if (messageId == -1) {
public static MessageViewFragment newInstance(long openerAccountId, long openerMailboxId,
long messageId) {
if (messageId == Message.NO_MESSAGE) {
throw new IllegalArgumentException();
}
final MessageViewFragment instance = new MessageViewFragment();
final Bundle args = new Bundle();
args.putLong(ARG_OPENER_ACCOUNT_ID, openerAccountId);
args.putLong(ARG_OPENER_MAILBOX_ID, openerMailboxId);
args.putLong(ARG_MESSAGE_ID, messageId);
instance.setArguments(args);
return instance;
@ -144,10 +166,14 @@ public class MessageViewFragment extends MessageViewFragmentBase
* constructs, this <em>must</em> be considered immutable.
*/
private Long mImmutableMessageId;
private Long mImmutableOpenerAccountId;
private Long mImmutableOpenerMailboxId;
private void initializeArgCache() {
if (mImmutableMessageId != null) return;
mImmutableMessageId = getArguments().getLong(ARG_MESSAGE_ID);
mImmutableOpenerAccountId = getArguments().getLong(ARG_OPENER_ACCOUNT_ID);
mImmutableOpenerMailboxId = getArguments().getLong(ARG_OPENER_MAILBOX_ID);
}
/**
@ -158,6 +184,22 @@ public class MessageViewFragment extends MessageViewFragmentBase
return mImmutableMessageId;
}
/**
* @return the account ID passed to {@link #newInstance}. Safe to call even before onCreate.
*/
public long getOpenerAccountId() {
initializeArgCache();
return mImmutableOpenerAccountId;
}
/**
* @return the mailbox ID passed to {@link #newInstance}. Safe to call even before onCreate.
*/
public long getOpenerMailboxId() {
initializeArgCache();
return mImmutableOpenerMailboxId;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View File

@ -341,6 +341,8 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
resetView();
new LoadMessageTask(true).executeParallel();
UiUtilities.installFragment(this);
}
@Override

View File

@ -26,28 +26,24 @@ import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.utility.EmailAsyncTask;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import java.util.ArrayList;
/**
* Base class for the UI controller.
*
* Note: Always use {@link #commitFragmentTransaction} to operate fragment transactions,
* so that we can easily switch between synchronous and asynchronous transactions.
*/
abstract class UIControllerBase {
protected static final String BUNDLE_KEY_ACCOUNT_ID = "UIController.state.account_id";
protected static final String BUNDLE_KEY_MAILBOX_ID = "UIController.state.mailbox_id";
protected static final String BUNDLE_KEY_MESSAGE_ID = "UIController.state.message_id";
abstract class UIControllerBase implements MailboxListFragment.Callback,
MessageListFragment.Callback, MessageViewFragment.Callback {
protected static final String BUNDLE_KEY_RESUME_INBOX_LOOKUP
= "UIController.state.resumeInboxLookup";
protected static final String BUNDLE_KEY_INBOX_LOOKUP_ACCOUNT_ID
@ -56,26 +52,15 @@ abstract class UIControllerBase {
/** The owner activity */
final EmailActivity mActivity;
private final ActionBarController mActionBarController;
final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker();
final RefreshManager mRefreshManager;
/**
* List of fragments that are restored by the framework while the activity is being re-created
* for configuration changes (e.g. screen rotation). We'll install them later when the activity
* is created in {@link #installRestoredFragments()}.
*/
private final ArrayList<Fragment> mRestoredFragments = new ArrayList<Fragment>();
/** {@code true} if the activity is resumed. */
private boolean mResumed;
/**
* Whether fragment installation should be hold.
* We hold installing fragments until {@link #installRestoredFragments()} is called.
*/
private boolean mHoldFragmentInstallation = true;
/**
* Use to find Inbox. This should only run while the activity is resumed, because otherwise
* we may not be able to perform fragment transactions when we get a callback.
@ -96,34 +81,47 @@ abstract class UIControllerBase {
*/
private boolean mResumeInboxLookup;
/**
* Fragments that are installed.
*
* A fragment is installed when:
* - it is attached to the activity
* - the parent activity is created
* - and it is not scheduled to be removed.
*
* We set callbacks to fragments only when they are installed.
*/
private MailboxListFragment mMailboxListFragment;
private MessageListFragment mMessageListFragment;
private MessageViewFragment mMessageViewFragment;
private final RefreshManager.Listener mRefreshListener
= new RefreshManager.Listener() {
@Override
public void onMessagingError(final long accountId, long mailboxId, final String message) {
updateRefreshProgress();
refreshActionBar();
}
@Override
public void onRefreshStatusChanged(long accountId, long mailboxId) {
updateRefreshProgress();
refreshActionBar();
}
};
public UIControllerBase(EmailActivity activity) {
mActivity = activity;
mRefreshManager = RefreshManager.getInstance(mActivity);
mActionBarController = createActionBarController(activity);
}
/**
* Called by the base class to let a subclass create an {@link ActionBarController}.
*/
protected abstract ActionBarController createActionBarController(Activity activity);
/** @return the layout ID for the activity. */
public abstract int getLayoutId();
/**
* @return true if the UI controller currently can install fragments.
*/
protected final boolean isFragmentInstallable() {
return !mHoldFragmentInstallation;
}
/**
* Must be called just after the activity sets up the content view. Used to initialize views.
*
@ -144,6 +142,7 @@ abstract class UIControllerBase {
Log.d(Logging.LOG_TAG, this + " onActivityCreated");
}
mRefreshManager.registerListener(mRefreshListener);
mActionBarController.onActivityCreated();
}
/**
@ -197,30 +196,10 @@ abstract class UIControllerBase {
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, this + " onActivityDestroy");
}
mHoldFragmentInstallation = true; // No more fragment installation.
mRefreshManager.unregisterListener(mRefreshListener);
mTaskTracker.cancellAllInterrupt();
}
/**
* Install all the fragments kept in {@link #mRestoredFragments}.
*
* Must be called at the end of {@link EmailActivity#onCreate}.
*/
public final void installRestoredFragments() {
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, this + " installRestoredFragments");
}
mHoldFragmentInstallation = false;
// Install all the fragments restored by the framework.
for (Fragment fragment : mRestoredFragments) {
installFragment(fragment);
}
mRestoredFragments.clear();
}
/**
* Handles the {@link android.app.Activity#onSaveInstanceState} callback.
*/
@ -244,24 +223,12 @@ abstract class UIControllerBase {
}
/**
* Handles the {@link android.app.Activity#onAttachFragment} callback.
*
* If the activity has already been created, we initialize the fragment here. Otherwise we
* keep the fragment in {@link #mRestoredFragments} and initialize it after the activity's
* onCreate.
* Install a fragment. Must be caleld from the host activity's
* {@link FragmentInstallable#onInstallFragment}.
*/
public final void onAttachFragment(Fragment fragment) {
if (mHoldFragmentInstallation) {
// Fragment being restored by the framework during the activity recreation.
mRestoredFragments.add(fragment);
return;
}
installFragment(fragment);
}
private void installFragment(Fragment fragment) {
public final void onInstallFragment(Fragment fragment) {
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, this + " installFragment fragment=" + fragment);
Log.d(Logging.LOG_TAG, this + " onInstallFragment fragment=" + fragment);
}
if (fragment instanceof MailboxListFragment) {
installMailboxListFragment((MailboxListFragment) fragment);
@ -270,15 +237,114 @@ abstract class UIControllerBase {
} else if (fragment instanceof MessageViewFragment) {
installMessageViewFragment((MessageViewFragment) fragment);
} else {
// Ignore -- uninteresting fragments such as dialogs.
throw new IllegalArgumentException("Tried to install unknown fragment");
}
}
protected abstract void installMailboxListFragment(MailboxListFragment fragment);
/** Install fragment */
protected void installMailboxListFragment(MailboxListFragment fragment) {
mMailboxListFragment = fragment;
mMailboxListFragment.setCallback(this);
refreshActionBar();
}
protected abstract void installMessageListFragment(MessageListFragment fragment);
/** Install fragment */
protected void installMessageListFragment(MessageListFragment fragment) {
mMessageListFragment = fragment;
mMessageListFragment.setCallback(this);
refreshActionBar();
}
protected abstract void installMessageViewFragment(MessageViewFragment fragment);
/** Install fragment */
protected void installMessageViewFragment(MessageViewFragment fragment) {
mMessageViewFragment = fragment;
mMessageViewFragment.setCallback(this);
refreshActionBar();
}
/**
* Uninstall - If the fragment is installed, remove it from the given
* {@link FragmentTransaction} and also clears the callabck.
*/
protected FragmentTransaction uninstallMailboxListFragment(FragmentTransaction ft) {
if (isMailboxListInstalled()) {
onMailboxListFragmentUninstalled(mMailboxListFragment);
ft.remove(mMailboxListFragment);
mMailboxListFragment.setCallback(null);
mMailboxListFragment = null;
}
return ft;
}
/** Called when a {@link MailboxListFragment} is about to be uninstalled */
protected void onMailboxListFragmentUninstalled(MailboxListFragment fragment) {
}
/**
* Uninstall - If the fragment is installed, remove it from the given
* {@link FragmentTransaction} and also clears the callabck.
*/
protected FragmentTransaction uninstallMessageListFragment(FragmentTransaction ft) {
if (isMessageListInstalled()) {
onMessageListFragmentUninstalled(mMessageListFragment);
ft.remove(mMessageListFragment);
mMessageListFragment.setCallback(null);
mMessageListFragment = null;
}
return ft;
}
/** Called when a {@link MessageListFragment} is about to be uninstalled */
protected void onMessageListFragmentUninstalled(MessageListFragment fragment) {
}
/**
* Uninstall - If the fragment is installed, remove it from the given
* {@link FragmentTransaction} and also clears the callabck.
*/
protected FragmentTransaction uninstallMessageViewFragment(FragmentTransaction ft) {
if (isMessageViewInstalled()) {
onMessageViewFragmentUninstalled(mMessageViewFragment);
ft.remove(mMessageViewFragment);
mMessageViewFragment.setCallback(null);
mMessageViewFragment = null;
}
return ft;
}
/** Called when a {@link MessageViewFragment} is about to be uninstalled */
protected void onMessageViewFragmentUninstalled(MessageViewFragment fragment) {
}
/** @return true if a {@link MailboxListFragment} is installed. */
protected final boolean isMailboxListInstalled() {
return mMailboxListFragment != null;
}
/** @return true if a {@link MessageListFragment} is installed. */
protected final boolean isMessageListInstalled() {
return mMessageListFragment != null;
}
/** @return true if a {@link MessageViewFragment} is installed. */
protected final boolean isMessageViewInstalled() {
return mMessageViewFragment != null;
}
/** @return the installed {@link MailboxListFragment} or null. */
protected final MailboxListFragment getMailboxListFragment() {
return mMailboxListFragment;
}
/** @return the installed {@link MessageListFragment} or null. */
protected final MessageListFragment getMessageListFragment() {
return mMessageListFragment;
}
/** @return the installed {@link MessageViewFragment} or null. */
protected final MessageViewFragment getMessageViewFragment() {
return mMessageViewFragment;
}
/**
* Commit a {@link FragmentTransaction}.
@ -535,14 +601,13 @@ abstract class UIControllerBase {
*/
protected abstract boolean isRefreshEnabled();
/**
* Start/stop the "refresh" animation on the action bar according to the current refresh state.
*
* (We start the animation if {@link #isRefreshInProgress} returns true,
* and stop otherwise.)
* Refresh the action bar and menu items, including the "refreshing" icon.
*/
protected void updateRefreshProgress() {
protected void refreshActionBar() {
if (mActionBarController != null) {
mActionBarController.refresh();
}
mActivity.invalidateOptionsMenu();
}
}

View File

@ -19,16 +19,21 @@ package com.android.email.activity;
import com.android.email.Email;
import com.android.email.R;
import com.android.email.activity.MailboxFinder.Callback;
import com.android.email.activity.setup.AccountSecurity;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent.Account;
import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.utility.Utility;
import android.app.FragmentManager;
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 java.util.Set;
@ -36,183 +41,179 @@ import java.util.Set;
/**
* UI Controller for non x-large devices. Supports a single-pane layout.
*
* STOPSHIP Everything in this class is 100% temporary at this point
* - Navigation model is different from what it should be (whatever it'll be).
* e.g. when the app is launched, we should show Inbox, not mailbox list.
* One one-pane, multiple fragments can be installed at the same time, but only one of them
* can be "visible" at a time. Others are in the back stack. Use {@link #isMailboxListVisible()},
* {@link #isMessageListVisible()} and {@link #isMessageViewVisible()} to determine which is
* visible.
*
* - It uses the two-pane action bar only so that we can change accounts
* Note due to the asynchronous nature of the fragment transaction, there is a window when
* there is no installed or visible fragments.
*
* TODO Use the back stack for the message list -> message view navigation, so that the list
* position/selection will be restored on back.
*
* Major TODOs
* - TODO Proper Navigation model, including retaining fragments to keep state such as the scroll
* position and batch selection.
* - TODO Nested folders
* - TODO Newer/Older for message view with swipe!
* - TODO Implement callbacks
*/
class UIControllerOnePane extends UIControllerBase {
private ActionBarController mActionBarController;
/**
* Current account/mailbox/message IDs.
* Don't use them directly; use the accessors instead, as we might want to get them from the
* topmost fragment in the future.
*/
private long mCurrentAccountId = Account.NO_ACCOUNT;
private long mCurrentMailboxId = Mailbox.NO_MAILBOX;
private long mCurrentMessageId = Message.NO_MESSAGE;
// TODO Newer/Older buttons not needed. Remove this.
private MessageCommandButtonView mMessageCommandButtons;
private final MailboxListFragment.Callback mMailboxListFragmentCallback =
new MailboxListFragment.Callback() {
@Override
public void onAccountSelected(long accountId) {
switchAccount(accountId);
// MailboxListFragment.Callback
@Override
public void onAccountSelected(long accountId) {
switchAccount(accountId);
}
// MailboxListFragment.Callback
@Override
public void onCurrentMailboxUpdated(long mailboxId, String mailboxName, int unreadCount) {
}
// MailboxListFragment.Callback
@Override
public void onMailboxSelected(long accountId, long mailboxId, boolean nestedNavigation) {
if (nestedNavigation) {
return; // Nothing to do on 1-pane.
}
openMailbox(accountId, mailboxId);
}
@Override
public void onCurrentMailboxUpdated(long mailboxId, String mailboxName, int unreadCount) {
// 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() {
open(getUIAccountId(), Mailbox.NO_MAILBOX, Message.NO_MESSAGE);
}
// 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(getUIAccountId(), getMailboxId(), messageId);
}
}
@Override
public void onMailboxSelected(long accountId, long mailboxId, boolean nestedNavigation) {
if (nestedNavigation) {
return; // Nothing to do on 1-pane.
}
openMailbox(accountId, mailboxId);
}
// MessageListFragment.Callback
@Override
public boolean onDragStarted() {
// No drag&drop on 1-pane
return false;
}
@Override
public void onParentMailboxChanged() {
refreshActionBar();
}
};
// MessageListFragment.Callback
@Override
public void onDragEnded() {
// No drag&drop on 1-pane
}
private final MessageListFragment.Callback mMessageListFragmentCallback =
new MessageListFragment.Callback() {
@Override
public void onAdvancingOpAccepted(Set<Long> affectedMessages) {
// Nothing to do on 1 pane.
}
// MessageViewFragment.Callback
@Override
public void onForward() {
MessageCompose.actionForward(mActivity, getMessageId());
}
@Override
public void onEnterSelectionMode(boolean enter) {
// TODO Auto-generated method stub
}
// MessageViewFragment.Callback
@Override
public void onReply() {
MessageCompose.actionReply(mActivity, getMessageId(), false);
}
@Override
public void onListLoaded() {
// TODO Auto-generated method stub
// MessageViewFragment.Callback
@Override
public void onReplyAll() {
MessageCompose.actionReply(mActivity, getMessageId(), true);
}
}
// MessageViewFragment.Callback
@Override
public void onCalendarLinkClicked(long epochEventStartTime) {
ActivityHelper.openCalendar(mActivity, epochEventStartTime);
}
@Override
public void onMailboxNotFound() {
open(getUIAccountId(), Mailbox.NO_MAILBOX, Message.NO_MESSAGE);
}
// MessageViewFragment.Callback
@Override
public boolean onUrlInMessageClicked(String url) {
return ActivityHelper.openUrlInMessage(mActivity, url, getActualAccountId());
}
@Override
public void onMessageOpen(
long messageId, long messageMailboxId, long listMailboxId, int type) {
if (type == MessageListFragment.Callback.TYPE_DRAFT) {
MessageCompose.actionEditDraft(mActivity, messageId);
} else {
open(getUIAccountId(), getMailboxId(), messageId);
}
}
// MessageViewFragment.Callback
@Override
public void onBeforeMessageGone() {
// TODO Auto-generated method stub
}
@Override
public boolean onDragStarted() {
// No drag&drop on 1-pane
return false;
}
// MessageViewFragment.Callback
@Override
public void onMessageSetUnread() {
// TODO Auto-generated method stub
}
@Override
public void onDragEnded() {
// No drag&drop on 1-pane
}
};
// MessageViewFragment.Callback
@Override
public void onRespondedToInvite(int response) {
// TODO Auto-generated method stub
}
private final MessageViewFragment.Callback mMessageViewFragmentCallback =
new MessageViewFragment.Callback() {
@Override
public void onForward() {
MessageCompose.actionForward(mActivity, getMessageId());
}
// MessageViewFragment.Callback
@Override
public void onLoadMessageError(String errorMessage) {
// TODO Auto-generated method stub
}
@Override
public void onReply() {
MessageCompose.actionReply(mActivity, getMessageId(), false);
}
// MessageViewFragment.Callback
@Override
public void onLoadMessageFinished() {
// TODO Auto-generated method stub
}
@Override
public void onReplyAll() {
MessageCompose.actionReply(mActivity, getMessageId(), true);
}
// MessageViewFragment.Callback
@Override
public void onLoadMessageStarted() {
// TODO Auto-generated method stub
}
@Override
public void onCalendarLinkClicked(long epochEventStartTime) {
ActivityHelper.openCalendar(mActivity, epochEventStartTime);
}
// MessageViewFragment.Callback
@Override
public void onMessageNotExists() {
// TODO Auto-generated method stub
}
@Override
public boolean onUrlInMessageClicked(String url) {
return ActivityHelper.openUrlInMessage(mActivity, url, getActualAccountId());
}
@Override
public void onBeforeMessageGone() {
// TODO Auto-generated method stub
}
@Override
public void onMessageSetUnread() {
// TODO Auto-generated method stub
}
@Override
public void onRespondedToInvite(int response) {
// TODO Auto-generated method stub
}
@Override
public void onLoadMessageError(String errorMessage) {
// TODO Auto-generated method stub
}
@Override
public void onLoadMessageFinished() {
// TODO Auto-generated method stub
}
@Override
public void onLoadMessageStarted() {
// TODO Auto-generated method stub
}
@Override
public void onMessageNotExists() {
// TODO Auto-generated method stub
}
@Override
public void onMessageShown() {
// TODO Auto-generated method stub
}
};
// MessageViewFragment.Callback
@Override
public void onMessageShown() {
// TODO Auto-generated method stub
}
// This is all temporary as we'll have a different action bar controller for 1-pane.
private final ActionBarController.Callback mActionBarControllerCallback
= new ActionBarController.Callback() {
private class ActionBarControllerCallback implements ActionBarController.Callback {
@Override
public boolean shouldShowMailboxName() {
return false; // no mailbox name/unread count.
@ -230,8 +231,8 @@ class UIControllerOnePane extends UIControllerBase {
@Override
public boolean shouldShowUp() {
// Always show the UP arrow.
return true;
return isMessageViewVisible()
|| (isMailboxListVisible() && !getMailboxListFragment().isRoot());
}
@Override
@ -254,37 +255,30 @@ class UIControllerOnePane extends UIControllerBase {
Welcome.actionStart(mActivity);
mActivity.finish();
}
};
}
public UIControllerOnePane(EmailActivity activity) {
super(activity);
}
/**
* {@inheritDoc}
*
* At this point we use synchronous transactions.
*/
@Override
protected void commitFragmentTransaction(FragmentTransaction ft) {
super.commitFragmentTransaction(ft);
mActivity.getFragmentManager().executePendingTransactions();
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);
outState.putLong(BUNDLE_KEY_ACCOUNT_ID, mCurrentAccountId);
outState.putLong(BUNDLE_KEY_MAILBOX_ID, mCurrentMailboxId);
outState.putLong(BUNDLE_KEY_MESSAGE_ID, mCurrentMessageId);
}
@Override
public void restoreInstanceState(Bundle savedInstanceState) {
super.restoreInstanceState(savedInstanceState);
mCurrentAccountId = savedInstanceState.getLong(BUNDLE_KEY_ACCOUNT_ID, Account.NO_ACCOUNT);
mCurrentMailboxId = savedInstanceState.getLong(BUNDLE_KEY_MAILBOX_ID, Mailbox.NO_MAILBOX);
mCurrentMessageId = savedInstanceState.getLong(BUNDLE_KEY_MESSAGE_ID, Message.NO_MESSAGE);
}
@Override
@ -295,8 +289,6 @@ class UIControllerOnePane extends UIControllerBase {
@Override
public void onActivityViewReady() {
super.onActivityViewReady();
mActionBarController = new ActionBarController(mActivity, mActivity.getLoaderManager(),
mActivity.getActionBar(), mActionBarControllerCallback);
mMessageCommandButtons = UiUtilities.getView(mActivity, R.id.message_command_buttons);
mMessageCommandButtons.setCallback(new CommandButtonCallback());
@ -305,7 +297,6 @@ class UIControllerOnePane extends UIControllerBase {
@Override
public void onActivityCreated() {
super.onActivityCreated();
mActionBarController.onActivityCreated();
}
@Override
@ -314,76 +305,96 @@ class UIControllerOnePane extends UIControllerBase {
refreshActionBar();
}
/** @return true if a {@link MailboxListFragment} is installed and visible. */
private final boolean isMailboxListVisible() {
return isMailboxListInstalled();
}
/** @return true if a {@link MessageListFragment} is installed and visible. */
private final boolean isMessageListVisible() {
return isMessageListInstalled();
}
/** @return true if a {@link MessageViewFragment} is installed and visible. */
private final boolean isMessageViewVisible() {
return isMessageViewInstalled();
}
@Override
public long getUIAccountId() {
return mCurrentAccountId;
// Get it from the visible fragment.
if (isMailboxListVisible()) {
return getMailboxListFragment().getAccountId();
}
if (isMessageListVisible()) {
return getMessageListFragment().getAccountId();
}
if (isMessageViewVisible()) {
return getMessageViewFragment().getOpenerAccountId();
}
return Account.NO_ACCOUNT;
}
private long getMailboxId() {
return mCurrentMailboxId;
// Get it from the visible fragment.
if (isMessageListVisible()) {
return getMessageListFragment().getMailboxId();
}
if (isMessageViewVisible()) {
return getMessageViewFragment().getOpenerMailboxId();
}
return Mailbox.NO_MAILBOX;
}
private long getMessageId() {
return mCurrentMessageId;
// Get it from the visible fragment.
if (isMessageViewVisible()) {
return getMessageViewFragment().getMessageId();
}
return Message.NO_MESSAGE;
}
private final MailboxFinder.Callback mInboxLookupCallback = new MailboxFinder.Callback() {
@Override
public void onMailboxFound(long accountId, long mailboxId) {
// Inbox found.
openMailbox(accountId, mailboxId);
}
@Override
public void onAccountNotFound() {
// Account removed?
Welcome.actionStart(mActivity);
}
@Override
public void onMailboxNotFound(long accountId) {
// Inbox not found??
Welcome.actionStart(mActivity);
}
@Override
public void onAccountSecurityHold(long accountId) {
mActivity.startActivity(AccountSecurity.actionUpdateSecurityIntent(mActivity, accountId,
true));
}
};
@Override
protected Callback getInboxLookupCallback() {
// We don't call startInboxLookup in UIControllerOnePane, so shouldn't be called.
throw new RuntimeException("SHOULD NOT BE CALLED"); // STOPSHIP
}
private void refreshActionBar() {
if (mActionBarController != null) {
mActionBarController.refresh();
}
mActivity.invalidateOptionsMenu();
}
private boolean isMailboxListVisible() {
return (getMailboxId() == Mailbox.NO_MAILBOX);
}
private boolean isMessageListVisible() {
return (getMailboxId() != Mailbox.NO_MAILBOX) && (getMessageId() == Message.NO_MESSAGE);
}
private boolean isMessageViewVisible() {
return (getMailboxId() != Mailbox.NO_MAILBOX) && (getMessageId() != Message.NO_MESSAGE);
return mInboxLookupCallback;
}
@Override
public boolean onBackPressed(boolean isSystemBackKey) {
if (isMessageViewVisible()) {
open(getUIAccountId(), getMailboxId(), Message.NO_MESSAGE);
openMailbox(getMessageViewFragment().getOpenerAccountId(),
getMessageViewFragment().getOpenerMailboxId());
return true;
} else if (isMessageListVisible()) {
open(getUIAccountId(), Mailbox.NO_MAILBOX, Message.NO_MESSAGE);
} else if (isMailboxListVisible() && getMailboxListFragment().navigateUp()) {
return true;
} else {
// TODO Call MailboxListFragment.navigateUp().
// STOPSHIP Remove this and return false. This is so that the app can be closed
// with the UP press. (usuful when the device doesn't have a HW back key.)
mActivity.finish();
return true;
// return false;
}
}
@Override
protected void installMailboxListFragment(MailboxListFragment fragment) {
fragment.setCallback(mMailboxListFragmentCallback);
}
@Override
protected void installMessageListFragment(MessageListFragment fragment) {
fragment.setCallback(mMessageListFragmentCallback);
}
@Override
protected void installMessageViewFragment(MessageViewFragment fragment) {
fragment.setCallback(mMessageViewFragmentCallback);
return false;
}
@Override
@ -396,40 +407,63 @@ class UIControllerOnePane extends UIControllerBase {
throw new IllegalArgumentException();
}
// !!! It's all temporary to make 1 pane UI (barely) usable !!!
//
// - Nested folders still doesn't work
// - When opening a child view (e.g. message list -> message view), we should retain
// the current fragment so that all the state (selection, scroll position, etc) will be
// restored when back.
if ((getUIAccountId() == accountId) && (getMailboxId() == mailboxId)
&& (getMessageId() == messageId)) {
return;
}
final FragmentManager fm = mActivity.getFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
if (messageId != Message.NO_MESSAGE) {
ft.replace(R.id.fragment_placeholder, MessageViewFragment.newInstance(messageId));
showMessageView(accountId, mailboxId, messageId);
} else if (mailboxId != Mailbox.NO_MAILBOX) {
ft.replace(R.id.fragment_placeholder, MessageListFragment.newInstance(
accountId, mailboxId));
showMessageList(accountId, mailboxId);
} else {
ft.replace(R.id.fragment_placeholder,
MailboxListFragment.newInstance(accountId, Mailbox.NO_MAILBOX, false));
// Mailbox not specified. Open Inbox or Combined Inbox.
if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
showMessageList(accountId, Mailbox.QUERY_ALL_INBOXES);
} else {
startInboxLookup(accountId);
}
}
}
mCurrentAccountId = accountId;
mCurrentMailboxId = mailboxId;
mCurrentMessageId = messageId;
private void uninstallAllFragments(FragmentTransaction ft) {
if (isMailboxListInstalled()) {
uninstallMailboxListFragment(ft);
}
if (isMessageListInstalled()) {
uninstallMessageListFragment(ft);
}
if (isMessageViewInstalled()) {
uninstallMessageViewFragment(ft);
}
}
private void showMailboxList(long accountId, long mailboxId) {
showFragment(MailboxListFragment.newInstance(accountId, mailboxId, false));
}
private void showMessageList(long accountId, long mailboxId) {
showFragment(MessageListFragment.newInstance(accountId, mailboxId));
}
private void showMessageView(long accountId, long mailboxId, long messageId) {
showFragment(MessageViewFragment.newInstance(accountId, mailboxId, messageId));
}
private void showFragment(Fragment fragment) {
final FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
uninstallAllFragments(ft);
ft.add(R.id.fragment_placeholder, fragment);
commitFragmentTransaction(ft);
}
refreshActionBar();
private void showAllMailboxes() {
if (!isAccountSelected()) {
return; // Can happen because of asyncronous fragment transactions.
}
// Don't use open(account, NO_MAILBOX, NO_MESSAGE). This is used to open the default
// view, which is Inbox on the message list.
showMailboxList(getUIAccountId(), Mailbox.NO_MAILBOX);
}
/*
@ -495,4 +529,21 @@ class UIControllerOnePane extends UIControllerBase {
return mRefreshManager.isMailboxListRefreshing(getActualAccountId());
}
}
@Override
public boolean onPrepareOptionsMenu(MenuInflater inflater, Menu menu) {
// STOPSHIP For temporary menu item which should be visible only on 1-pane.
menu.findItem(R.id.show_all_folders).setVisible(true);
return super.onPrepareOptionsMenu(inflater, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.show_all_folders: // STOPSHIP For temporary menu item
showAllMailboxes();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -54,27 +54,9 @@ class UIControllerTwoPane extends UIControllerBase implements
@VisibleForTesting
static final int INBOX_AUTO_REFRESH_MIN_INTERVAL = 10 * 1000; // in milliseconds
private ActionBarController mActionBarController;
private final ActionBarControllerCallback mActionBarControllerCallback =
new ActionBarControllerCallback();
// Other UI elements
private ThreePaneLayout mThreePane;
/**
* Fragments that are installed.
*
* A fragment is installed when:
* - it is attached to the activity
* - the parent activity is created
* - and it is not scheduled to be removed.
*
* We set callbacks to fragments only when they are installed.
*/
private MailboxListFragment mMailboxListFragment;
private MessageListFragment mMessageListFragment;
private MessageViewFragment mMessageViewFragment;
private MessageCommandButtonView mMessageCommandButtons;
private MessageOrderManager mOrderManager;
@ -100,12 +82,6 @@ class UIControllerTwoPane extends UIControllerBase implements
FragmentManager.enableDebugLogging(true);
}
private void refreshActionBar() {
if (mActionBarController != null) {
mActionBarController.refresh();
}
}
@Override
public int getLayoutId() {
return R.layout.email_activity_two_pane;
@ -163,7 +139,7 @@ class UIControllerTwoPane extends UIControllerBase implements
}
// Disable CAB when the message list is not visible.
if (isMessageListInstalled()) {
mMessageListFragment.onHidden((visiblePanes & ThreePaneLayout.PANE_MIDDLE) == 0);
getMessageListFragment().onHidden((visiblePanes & ThreePaneLayout.PANE_MIDDLE) == 0);
}
}
@ -377,8 +353,6 @@ class UIControllerTwoPane extends UIControllerBase implements
@Override
public void onActivityViewReady() {
super.onActivityViewReady();
mActionBarController = new ActionBarController(mActivity, mActivity.getLoaderManager(),
mActivity.getActionBar(), mActionBarControllerCallback);
// Set up content
mThreePane = (ThreePaneLayout) mActivity.findViewById(R.id.three_pane);
@ -388,6 +362,12 @@ class UIControllerTwoPane extends UIControllerBase implements
mMessageCommandButtons.setCallback(new CommandButtonCallback());
}
@Override
protected ActionBarController createActionBarController(Activity activity) {
return new ActionBarController(activity, activity.getLoaderManager(),
activity.getActionBar(), new ActionBarControllerCallback());
}
/**
* @return the currently selected account ID, *or* {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
*
@ -395,7 +375,7 @@ class UIControllerTwoPane extends UIControllerBase implements
*/
@Override
public long getUIAccountId() {
return isMailboxListInstalled() ? mMailboxListFragment.getAccountId()
return isMailboxListInstalled() ? getMailboxListFragment().getAccountId()
:Account.NO_ACCOUNT;
}
@ -406,7 +386,7 @@ class UIControllerTwoPane extends UIControllerBase implements
* {@link #getMessageListMailboxId()}
*/
private long getMailboxListMailboxId() {
return isMailboxListInstalled() ? mMailboxListFragment.getSelectedMailboxId()
return isMailboxListInstalled() ? getMailboxListFragment().getSelectedMailboxId()
: Mailbox.NO_MAILBOX;
}
@ -417,7 +397,7 @@ class UIControllerTwoPane extends UIControllerBase implements
* {@link #getMessageListMailboxId()}
*/
private long getMessageListMailboxId() {
return isMessageListInstalled() ? mMessageListFragment.getMailboxId()
return isMessageListInstalled() ? getMessageListFragment().getMailboxId()
: Message.NO_MESSAGE;
}
@ -438,23 +418,10 @@ class UIControllerTwoPane extends UIControllerBase implements
}
private long getMessageId() {
return isMessageViewInstalled() ? mMessageViewFragment.getMessageId()
return isMessageViewInstalled() ? getMessageViewFragment().getMessageId()
: Message.NO_MESSAGE;
}
private boolean isMailboxListInstalled() {
return mMailboxListFragment != null;
}
private boolean isMessageListInstalled() {
return mMessageListFragment != null;
}
private boolean isMessageViewInstalled() {
return mMessageViewFragment != null;
}
/**
* @return true if refresh is in progress for the current mailbox.
*/
@ -481,7 +448,6 @@ class UIControllerTwoPane extends UIControllerBase implements
@Override
public void onActivityCreated() {
super.onActivityCreated();
mActionBarController.onActivityCreated();
}
/** {@inheritDoc} */
@ -536,67 +502,28 @@ class UIControllerTwoPane extends UIControllerBase implements
return this;
}
@Override
protected void installMailboxListFragment(MailboxListFragment fragment) {
mMailboxListFragment = fragment;
mMailboxListFragment.setCallback(this);
// Update action bar / menu
updateRefreshProgress();
refreshActionBar();
}
@Override
protected void installMessageListFragment(MessageListFragment fragment) {
mMessageListFragment = fragment;
mMessageListFragment.setCallback(this);
super.installMessageListFragment(fragment);
if (isMailboxListInstalled()) {
mMailboxListFragment.setHighlightedMailbox(mMessageListFragment.getMailboxId());
getMailboxListFragment().setHighlightedMailbox(fragment.getMailboxId());
}
// Update action bar / menu
updateRefreshProgress();
refreshActionBar();
}
@Override
protected void installMessageViewFragment(MessageViewFragment fragment) {
mMessageViewFragment = fragment;
mMessageViewFragment.setCallback(this);
super.installMessageViewFragment(fragment);
if (isMessageListInstalled()) {
mMessageListFragment.setSelectedMessage(mMessageViewFragment.getMessageId());
getMessageListFragment().setSelectedMessage(fragment.getMessageId());
}
}
private FragmentTransaction uninstallMailboxListFragment(FragmentTransaction ft) {
if (isMailboxListInstalled()) {
ft.remove(mMailboxListFragment);
mMailboxListFragment.setCallback(null);
mMailboxListFragment = null;
}
return ft;
}
private FragmentTransaction uninstallMessageListFragment(FragmentTransaction ft) {
if (isMessageListInstalled()) {
ft.remove(mMessageListFragment);
mMessageListFragment.setCallback(null);
mMessageListFragment = null;
}
return ft;
}
private FragmentTransaction uninstallMessageViewFragment(FragmentTransaction ft) {
if (isMessageViewInstalled()) {
ft.remove(mMessageViewFragment);
mMessageViewFragment.setCallback(null);
mMessageViewFragment = null;
// Don't need it when there's no message view.
stopMessageOrderManager();
}
return ft;
@Override
protected void onMessageViewFragmentUninstalled(MessageViewFragment fragment) {
// Don't need it when there's no message view.
stopMessageOrderManager();
}
/**
@ -638,20 +565,6 @@ class UIControllerTwoPane extends UIControllerBase implements
commitFragmentTransaction(ft);
}
/**
* Pre-fragment transaction check.
*
* @throw IllegalStateException if updateXxx methods can't be called in the current state.
*/
private void preFragmentTransactionCheck() {
if (!isFragmentInstallable()) {
// Code assumes mMailboxListFragment/etc are set right within the
// commitFragmentTransaction() call (because we use synchronous transaction),
// so updateXxx() can't be called if fragments are not installable yet.
throw new IllegalStateException();
}
}
/**
* Loads the given account and optionally selects the given mailbox and message. If the
* specified account is already selected, no actions will be performed unless
@ -669,12 +582,10 @@ class UIControllerTwoPane extends UIControllerBase implements
Log.d(Logging.LOG_TAG, this + " updateMailboxList accountId=" + accountId
+ " mailboxId=" + mailboxId);
}
preFragmentTransactionCheck();
if (accountId == Account.NO_ACCOUNT) {
throw new IllegalArgumentException();
}
if ((getUIAccountId() != accountId) || (getMailboxListMailboxId() != mailboxId)) {
uninstallMailboxListFragment(ft);
ft.add(mThreePane.getLeftPaneId(),
@ -721,7 +632,6 @@ class UIControllerTwoPane extends UIControllerBase implements
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, this + " updateMessageList mMailboxId=" + mailboxId);
}
preFragmentTransactionCheck();
if (mailboxId == Mailbox.NO_MAILBOX) {
throw new IllegalArgumentException();
}
@ -758,7 +668,6 @@ class UIControllerTwoPane extends UIControllerBase implements
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, this + " updateMessageView messageId=" + messageId);
}
preFragmentTransactionCheck();
if (messageId == Message.NO_MESSAGE) {
throw new IllegalArgumentException();
}
@ -768,7 +677,9 @@ class UIControllerTwoPane extends UIControllerBase implements
}
uninstallMessageViewFragment(ft);
ft.add(mThreePane.getRightPaneId(), MessageViewFragment.newInstance(messageId));
ft.add(mThreePane.getRightPaneId(), MessageViewFragment.newInstance(
getUIAccountId(), getMessageListMailboxId(), messageId));
}
/**
@ -786,8 +697,8 @@ class UIControllerTwoPane extends UIControllerBase implements
private void unselectMessage() {
commitFragmentTransaction(uninstallMessageViewFragment(
mActivity.getFragmentManager().beginTransaction()));
if (mMessageListFragment != null) {
mMessageListFragment.setSelectedMessage(Message.NO_MESSAGE);
if (isMessageListInstalled()) {
getMessageListFragment().setSelectedMessage(Message.NO_MESSAGE);
}
}
@ -891,7 +802,7 @@ class UIControllerTwoPane extends UIControllerBase implements
public boolean onBackPressed(boolean isSystemBackKey) {
if (mThreePane.onBackPressed(isSystemBackKey)) {
return true;
} else if (isMailboxListInstalled() && mMailboxListFragment.navigateUp()) {
} else if (isMailboxListInstalled() && getMailboxListFragment().navigateUp()) {
return true;
}
return false;
@ -1061,7 +972,7 @@ class UIControllerTwoPane extends UIControllerBase implements
final int visiblePanes = mThreePane.getVisiblePanes();
final boolean leftPaneHidden = ((visiblePanes & ThreePaneLayout.PANE_LEFT) == 0);
return leftPaneHidden
|| (isMailboxListInstalled() && !mMailboxListFragment.isRoot());
|| (isMailboxListInstalled() && !getMailboxListFragment().isRoot());
}
}
}

View File

@ -19,6 +19,7 @@ package com.android.email.activity;
import com.android.email.R;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.content.res.Resources;
import android.view.View;
@ -123,4 +124,16 @@ public class UiUtilities {
public static void setVisibilitySafe(View parent, int viewId, int visibility) {
setVisibilitySafe(parent.findViewById(viewId), visibility);
}
/**
* Used by an {@link Fragment} to install itself to the host activity.
*
* @see FragmentInstallable
*/
public static void installFragment(Fragment fragment) {
final Activity a = fragment.getActivity();
if (a instanceof FragmentInstallable) {
((FragmentInstallable) a).onInstallFragment(fragment);
}
}
}