From f5492ea991d3b296b8158f6ea0e85cdbae5941ed Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Mon, 9 May 2011 14:31:11 -0700 Subject: [PATCH] Add 1-pane UI controller Added the base class for the UI controllers and the 1-pane implementaion, which is almost empty at this point. The old phone activities still exist and will be used on the phone by default. To use the new activity (1-pane EmailActivity) on the phone, use the following comamnd. adb shell am start -a android.intent.action.MAIN \ -d '"content://ui.email.android.com/view/mailbox"' \ -e DEBUG_PANE_MODE 1 Change-Id: Id1fe85d4517778afc967d7d5e17e1299dd1bfefd --- res/layout/email_activity_one_pane.xml | 44 +++ ...ist_xl.xml => email_activity_two_pane.xml} | 0 ..._option.xml => email_activity_options.xml} | 0 res/values-xlarge/bools.xml | 19 ++ res/values/bools.xml | 19 ++ .../android/email/activity/EmailActivity.java | 88 +++-- .../email/activity/MessageCompose.java | 2 +- .../email/activity/UIControllerBase.java | 301 ++++++++++++++++++ .../email/activity/UIControllerOnePane.java | 89 ++++++ .../email/activity/UIControllerTwoPane.java | 224 +++++-------- src/com/android/email/activity/Welcome.java | 40 ++- 11 files changed, 647 insertions(+), 179 deletions(-) create mode 100644 res/layout/email_activity_one_pane.xml rename res/layout/{message_list_xl.xml => email_activity_two_pane.xml} (100%) rename res/menu/{message_list_xl_option.xml => email_activity_options.xml} (100%) create mode 100644 res/values-xlarge/bools.xml create mode 100644 res/values/bools.xml create mode 100644 src/com/android/email/activity/UIControllerBase.java create mode 100644 src/com/android/email/activity/UIControllerOnePane.java diff --git a/res/layout/email_activity_one_pane.xml b/res/layout/email_activity_one_pane.xml new file mode 100644 index 000000000..0da27c7da --- /dev/null +++ b/res/layout/email_activity_one_pane.xml @@ -0,0 +1,44 @@ + + + + + + + + + + diff --git a/res/layout/message_list_xl.xml b/res/layout/email_activity_two_pane.xml similarity index 100% rename from res/layout/message_list_xl.xml rename to res/layout/email_activity_two_pane.xml diff --git a/res/menu/message_list_xl_option.xml b/res/menu/email_activity_options.xml similarity index 100% rename from res/menu/message_list_xl_option.xml rename to res/menu/email_activity_options.xml diff --git a/res/values-xlarge/bools.xml b/res/values-xlarge/bools.xml new file mode 100644 index 000000000..b6bbe2ea3 --- /dev/null +++ b/res/values-xlarge/bools.xml @@ -0,0 +1,19 @@ + + + + + true + diff --git a/res/values/bools.xml b/res/values/bools.xml new file mode 100644 index 000000000..09c130c01 --- /dev/null +++ b/res/values/bools.xml @@ -0,0 +1,19 @@ + + + + + false + diff --git a/src/com/android/email/activity/EmailActivity.java b/src/com/android/email/activity/EmailActivity.java index b312d4fa7..0451d2f81 100644 --- a/src/com/android/email/activity/EmailActivity.java +++ b/src/com/android/email/activity/EmailActivity.java @@ -64,6 +64,7 @@ public class EmailActivity extends Activity implements View.OnClickListener { 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"; + private static final String EXTRA_FORCE_PANE_MODE = "FORCE_PANE_MODE"; /** Loader IDs starting with this is safe to use from UIControllers. */ static final int UI_CONTROLLER_LOADER_ID_BASE = 100; @@ -78,7 +79,7 @@ public class EmailActivity extends Activity implements View.OnClickListener { private Controller mController; private Controller.Result mControllerResult; - private final UIControllerTwoPane mUIController = new UIControllerTwoPane(this); + private UIControllerBase mUIController; private final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker(); @@ -91,45 +92,46 @@ public class EmailActivity extends Activity implements View.OnClickListener { private int mDialogSelection = -1; /** - * Launch and open account's inbox. + * Create an intent to launch and open account's inbox. * * @param accountId If -1, default account will be used. */ - public static void actionOpenAccount(Activity fromActivity, long accountId) { + public static Intent createOpenAccountIntent(Activity fromActivity, long accountId) { Intent i = IntentUtilities.createRestartAppIntent(fromActivity, EmailActivity.class); if (accountId != -1) { i.putExtra(EXTRA_ACCOUNT_ID, accountId); } - fromActivity.startActivity(i); + return i; } /** - * Launch and open a mailbox. + * Create an intent to launch and open a mailbox. * * @param accountId must not be -1. * @param mailboxId must not be -1. Magic mailboxes IDs (such as * {@link Mailbox#QUERY_ALL_INBOXES}) don't work. */ - public static void actionOpenMailbox(Activity fromActivity, long accountId, long mailboxId) { + public static Intent createOpenMailboxIntent(Activity fromActivity, long accountId, + long mailboxId) { if (accountId == -1 || mailboxId == -1) { throw new InvalidParameterException(); } Intent i = IntentUtilities.createRestartAppIntent(fromActivity, EmailActivity.class); i.putExtra(EXTRA_ACCOUNT_ID, accountId); i.putExtra(EXTRA_MAILBOX_ID, mailboxId); - fromActivity.startActivity(i); + return i; } /** - * Launch and open a message. + * Create an intent to launch and open a message. * * @param accountId must not be -1. * @param mailboxId must not be -1. Magic mailboxes IDs (such as * {@link Mailbox#QUERY_ALL_INBOXES}) don't work. * @param messageId must not be -1. */ - public static void actionOpenMessage(Activity fromActivity, long accountId, long mailboxId, - long messageId) { + public static Intent createOpenMessageIntent(Activity fromActivity, long accountId, + long mailboxId, long messageId) { if (accountId == -1 || mailboxId == -1 || messageId == -1) { throw new InvalidParameterException(); } @@ -137,15 +139,48 @@ public class EmailActivity extends Activity implements View.OnClickListener { i.putExtra(EXTRA_ACCOUNT_ID, accountId); i.putExtra(EXTRA_MAILBOX_ID, mailboxId); i.putExtra(EXTRA_MESSAGE_ID, messageId); - fromActivity.startActivity(i); + return i; + } + + /** + * Set a debug flag to an intent to force open in 1-pane or 2-pane. + * + * @param useTwoPane true to open in 2-pane. false to open in 1-pane. + */ + public static void forcePaneMode(Intent i, boolean useTwoPane) { + i.putExtra(EXTRA_FORCE_PANE_MODE, useTwoPane ? 2 : 1); + } + + /** + * Initialize {@link #mUIController}. + */ + private void initUIController() { + final boolean twoPane; + switch (getIntent().getIntExtra(EXTRA_FORCE_PANE_MODE, -1)) { + case 1: + twoPane = false; + break; + case 2: + twoPane = true; + break; + default: + twoPane = getResources().getBoolean(R.bool.use_two_pane); + break; + } + mUIController = twoPane ? new UIControllerTwoPane(this) : new UIControllerOnePane(this); } @Override protected void onCreate(Bundle savedInstanceState) { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, "" + this + " onCreate"); + + // UIController is used in onPrepareOptionsMenu(), which can be called from within + // super.onCreate(), so we need to initialize it here. + initUIController(); + super.onCreate(savedInstanceState); ActivityHelper.debugSetWindowFlags(this); - setContentView(R.layout.message_list_xl); + setContentView(mUIController.getLayoutId()); mUIController.onActivityViewReady(); @@ -212,7 +247,7 @@ public class EmailActivity extends Activity implements View.OnClickListener { protected void onStart() { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, "" + this + " onStart"); super.onStart(); - mUIController.onStart(); + mUIController.onActivityStart(); // STOPSHIP Temporary search UI Intent intent = getIntent(); @@ -245,7 +280,8 @@ public class EmailActivity extends Activity implements View.OnClickListener { msg.mTimeStamp = Long.MAX_VALUE; // Sort on top msg.save(mContext); - actionOpenMessage(EmailActivity.this, accountId, searchMailbox.mId, msg.mId); + startActivity(createOpenMessageIntent(EmailActivity.this, + accountId, searchMailbox.mId, msg.mId)); Utility.runAsync(new Runnable() { @Override public void run() { @@ -260,7 +296,7 @@ public class EmailActivity extends Activity implements View.OnClickListener { protected void onResume() { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, "" + this + " onResume"); super.onResume(); - mUIController.onResume(); + mUIController.onActivityResume(); /** * In {@link MessageList#onResume()}, we go back to {@link Welcome} if an account * has been added/removed. We don't need to do that here, because we fetch the most @@ -273,14 +309,14 @@ public class EmailActivity extends Activity implements View.OnClickListener { protected void onPause() { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, "" + this + " onPause"); super.onPause(); - mUIController.onPause(); + mUIController.onActivityPause(); } @Override protected void onStop() { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, "" + this + " onStop"); super.onStop(); - mUIController.onStop(); + mUIController.onActivityStop(); } @Override @@ -288,7 +324,7 @@ public class EmailActivity extends Activity implements View.OnClickListener { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, "" + this + " onDestroy"); mController.removeResultCallback(mControllerResult); mTaskTracker.cancellAllInterrupt(); - mUIController.onDestroy(); + mUIController.onActivityDestroy(); super.onDestroy(); } @@ -348,14 +384,13 @@ public class EmailActivity extends Activity implements View.OnClickListener { public boolean onSearchRequested() { Bundle bundle = new Bundle(); bundle.putLong(EXTRA_ACCOUNT_ID, mUIController.getActualAccountId()); - bundle.putLong(EXTRA_MAILBOX_ID, mUIController.getMessageListMailboxId()); + bundle.putLong(EXTRA_MAILBOX_ID, mUIController.getSearchMailboxId()); startSearch(null, false, bundle, false); return true; } // STOPSHIP Set column from user options - private void setMailboxColumn(String column, String value) { - final long mailboxId = mUIController.getMessageListMailboxId(); + private void setMailboxColumn(long mailboxId, String column, String value) { if (mailboxId > 0) { ContentValues cv = new ContentValues(); cv.put(column, value); @@ -397,8 +432,11 @@ public class EmailActivity extends Activity implements View.OnClickListener { @Override @Deprecated protected Dialog onCreateDialog(int id, Bundle args) { - Mailbox mailbox - = Mailbox.restoreMailboxWithId(this, mUIController.getMessageListMailboxId()); + final long mailboxId = mUIController.getMailboxSettingsMailboxId(); + if (mailboxId < 0) { + return null; + } + final Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mailboxId); if (mailbox == null) return null; switch (id) { case MAILBOX_SYNC_FREQUENCY_DIALOG: @@ -416,7 +454,7 @@ public class EmailActivity extends Activity implements View.OnClickListener { mSelectionListener) .setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - setMailboxColumn(MailboxColumns.SYNC_INTERVAL, + setMailboxColumn(mailboxId, MailboxColumns.SYNC_INTERVAL, freqValues[mDialogSelection]); }}) .setNegativeButton(R.string.cancel_action, mCancelListener) @@ -435,7 +473,7 @@ public class EmailActivity extends Activity implements View.OnClickListener { mSelectionListener) .setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - setMailboxColumn(MailboxColumns.SYNC_LOOKBACK, + setMailboxColumn(mailboxId, MailboxColumns.SYNC_LOOKBACK, windowValues[mDialogSelection]); }}) .setNegativeButton(R.string.cancel_action, mCancelListener) diff --git a/src/com/android/email/activity/MessageCompose.java b/src/com/android/email/activity/MessageCompose.java index 54e07067a..0f872c0cf 100644 --- a/src/com/android/email/activity/MessageCompose.java +++ b/src/com/android/email/activity/MessageCompose.java @@ -290,7 +290,7 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus if (accountId == -1) { // There are no accounts set up. This should not have happened. Prompt the // user to set up an account as an acceptable bailout. - AccountFolderList.actionShowAccounts(this); + Welcome.actionStart(this); finish(); } else { setAccount(Account.restoreAccountWithId(this, accountId)); diff --git a/src/com/android/email/activity/UIControllerBase.java b/src/com/android/email/activity/UIControllerBase.java new file mode 100644 index 000000000..8895276ef --- /dev/null +++ b/src/com/android/email/activity/UIControllerBase.java @@ -0,0 +1,301 @@ +/* + * 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 com.android.email.Email; +import com.android.email.RefreshManager; +import com.android.emailcommon.Logging; +import com.android.emailcommon.provider.EmailContent.Account; +import com.android.emailcommon.utility.EmailAsyncTask; + +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.ArrayList; + +/** + * Base class for the UI controller. + * + * Note: Always use {@link #commitFragmentTransaction} to commit fragment transactions. + * (Currently we use synchronous transactions only, but we may want to switch back to asynchronous + * later.) + */ +abstract class UIControllerBase { + /** No account selected */ + static final long NO_ACCOUNT = -1; + /** No mailbox selected */ + static final long NO_MAILBOX = -1; + /** No message selected */ + static final long NO_MESSAGE = -1; + + /** The owner activity */ + final EmailActivity mActivity; + + final RefreshManager mRefreshManager; + + final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker(); + + /** + * 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 mRestoredFragments = new ArrayList(); + + /** + * Whether fragment installation should be hold. + * We hold installing fragments until {@link #installRestoredFragments()} is called. + */ + private boolean mHoldFragmentInstallation = true; + + public UIControllerBase(EmailActivity activity) { + mActivity = activity; + mRefreshManager = RefreshManager.getInstance(mActivity); + } + + /** @return the layout ID for the activity. */ + public abstract int getLayoutId(); + + /** + * @return true if the UI controller currently can install fragments. + */ + boolean isFragmentInstallable() { + return !mHoldFragmentInstallation; + } + + /** + * Must be called just after the activity sets up the content view. Used to initialize views. + * + * (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, this + " onActivityViewReady"); + } + } + + /** + * Called at the end of {@link EmailActivity#onCreate}. + */ + public void onActivityCreated() { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Logging.LOG_TAG, this + " onActivityCreated"); + } + } + + /** + * Handles the {@link android.app.Activity#onStart} callback. + */ + public void onActivityStart() { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Logging.LOG_TAG, this + " onActivityStart"); + } + } + + /** + * Handles the {@link android.app.Activity#onResume} callback. + */ + public void onActivityResume() { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Logging.LOG_TAG, this + " onActivityResume"); + } + } + + /** + * Handles the {@link android.app.Activity#onPause} callback. + */ + public void onActivityPause() { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Logging.LOG_TAG, this + " onActivityPause"); + } + } + + /** + * Handles the {@link android.app.Activity#onStop} callback. + */ + public void onActivityStop() { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Logging.LOG_TAG, this + " onActivityStop"); + } + } + + /** + * Handles the {@link android.app.Activity#onDestroy} callback. + */ + public void onActivityDestroy() { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Logging.LOG_TAG, this + " onActivityDestroy"); + } + mHoldFragmentInstallation = true; // No more fragment installation. + 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 (Email.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. + */ + public void onSaveInstanceState(Bundle outState) { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Logging.LOG_TAG, this + " onSaveInstanceState"); + } + } + + /** + * Handles the {@link android.app.Activity#onRestoreInstanceState} callback. + */ + public void restoreInstanceState(Bundle savedInstanceState) { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Logging.LOG_TAG, this + " restoreInstanceState"); + } + } + + /** + * 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. + */ + public final void onAttachFragment(Fragment fragment) { + if (mHoldFragmentInstallation) { + // Fragment being restored by the framework during the activity recreation. + mRestoredFragments.add(fragment); + return; + } + installFragment(fragment); + } + + void installFragment(Fragment fragment) { + if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { + Log.d(Logging.LOG_TAG, this + " installFragment fragment=" + fragment); + } + } + + void commitFragmentTransaction(FragmentTransaction ft) { + ft.commit(); + mActivity.getFragmentManager().executePendingTransactions(); + } + + + /** + * @return the currently selected account ID, *or* {@link Account#ACCOUNT_ID_COMBINED_VIEW}. + * + * @see #getActualAccountId() + */ + public abstract long getUIAccountId(); + + /** + * @return the currently selected account ID. If the current view is the combined view, + * it'll return {@link #NO_ACCOUNT}. + * + * @see #getUIAccountId() + */ + public long getActualAccountId() { + final long uiAccountId = getUIAccountId(); + return uiAccountId == Account.ACCOUNT_ID_COMBINED_VIEW ? NO_ACCOUNT : uiAccountId; + } + + /** + * Show the default view for the given account. + * + * @param accountId ID of the account to load. Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}. + * Must never be {@link #NO_ACCOUNT}. + */ + public abstract void openAccount(long accountId); + + /** + * Loads the given account and optionally selects the given mailbox and message. Used to open + * a particular view at a request from outside of the activity, such as the widget. + * + * @param accountId ID of the account to load. Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}. + * Must never be {@link #NO_ACCOUNT}. + * @param mailboxId ID of the mailbox to load. If {@link #NO_MAILBOX}, load the account's inbox. + * @param messageId ID of the message to load. If {@link #NO_MESSAGE}, do not open a message. + */ + public abstract void open(long accountId, long mailboxId, long messageId); + + /** + * Performs the back action. + * + * @param isSystemBackKey true if the system back key was pressed. + * false if it's caused by the "home" icon click on the action bar. + */ + public abstract boolean onBackPressed(boolean isSystemBackKey); + + /** + * Handles the {@link android.app.Activity#onCreateOptionsMenu} callback. + */ + public abstract boolean onCreateOptionsMenu(MenuInflater inflater, Menu menu); + + /** + * Handles the {@link android.app.Activity#onPrepareOptionsMenu} callback. + */ + public abstract boolean onPrepareOptionsMenu(MenuInflater inflater, Menu menu); + + /** + * Handles the {@link android.app.Activity#onOptionsItemSelected} callback. + * + * @return true if the option item is handled. + */ + public abstract boolean onOptionsItemSelected(MenuItem item); + + /** + * STOPSHIP For experimental UI. Remove this. + * + * @return mailbox ID which we search for messages. + */ + public abstract long getSearchMailboxId(); + + /** + * STOPSHIP For experimental UI. Remove this. + * + * @return mailbox ID for "mailbox settings" option. + */ + public abstract long getMailboxSettingsMailboxId(); + + /** + * STOPSHIP For experimental UI. Remove this. + * + * Performs "refesh". + */ + public void onRefresh() { + } +} diff --git a/src/com/android/email/activity/UIControllerOnePane.java b/src/com/android/email/activity/UIControllerOnePane.java new file mode 100644 index 000000000..707e49d1b --- /dev/null +++ b/src/com/android/email/activity/UIControllerOnePane.java @@ -0,0 +1,89 @@ +/* + * 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 com.android.email.R; + +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + + +/** + * UI Controller for non x-large devices. Supports a single-pane layout. + */ +class UIControllerOnePane extends UIControllerBase { + public UIControllerOnePane(EmailActivity activity) { + super(activity); + } + + @Override + public int getLayoutId() { + return R.layout.email_activity_one_pane; + } + + @Override + public long getUIAccountId() { + return -1; + } + + @Override + public boolean onBackPressed(boolean isSystemBackKey) { + return false; + } + + @Override + public boolean onCreateOptionsMenu(MenuInflater inflater, Menu menu) { + return false; + } + + @Override + public boolean onPrepareOptionsMenu(MenuInflater inflater, Menu menu) { + return false; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return false; + } + + @Override + public void open(long accountId, long mailboxId, long messageId) { + } + + @Override + public void openAccount(long accountId) { + } + + /* + * STOPSHIP Remove this -- see the base class method. + */ + @Override + public long getMailboxSettingsMailboxId() { + // Mailbox settigns is still experimental, and doesn't have to work on the phone. + return -1; + } + + /* + * STOPSHIP Remove this -- see the base class method. + */ + @Override + public long getSearchMailboxId() { + // Search is still experimental, and doesn't have to work on the phone. + return -1; + } +} diff --git a/src/com/android/email/activity/UIControllerTwoPane.java b/src/com/android/email/activity/UIControllerTwoPane.java index 13afb364d..9f81c891e 100644 --- a/src/com/android/email/activity/UIControllerTwoPane.java +++ b/src/com/android/email/activity/UIControllerTwoPane.java @@ -41,22 +41,13 @@ import android.view.MenuInflater; import android.view.MenuItem; import java.security.InvalidParameterException; -import java.util.ArrayList; import java.util.Set; import java.util.Stack; /** * UI Controller for x-large devices. Supports a multi-pane layout. - * - * Note: Always use {@link #commitFragmentTransaction} to commit fragment transactions. Currently - * we use synchronous transactions only, but we may want to switch back to asynchronous later. - * - * 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 Consider extracting out a separate class to manage the action bar */ -class UIControllerTwoPane implements +class UIControllerTwoPane extends UIControllerBase implements MailboxFinder.Callback, ThreePaneLayout.Callback, MailboxListFragment.Callback, @@ -71,12 +62,6 @@ class UIControllerTwoPane implements /* package */ static final int MAILBOX_REFRESH_MIN_INTERVAL = 30 * 1000; // in milliseconds /* package */ static final int INBOX_AUTO_REFRESH_MIN_INTERVAL = 10 * 1000; // in milliseconds - /** No account selected */ - static final long NO_ACCOUNT = -1; - /** No mailbox selected */ - static final long NO_MAILBOX = -1; - /** No message selected */ - static final long NO_MESSAGE = -1; /** Current account id */ private long mAccountId = NO_ACCOUNT; @@ -117,7 +102,6 @@ class UIControllerTwoPane implements private MailboxFinder mMailboxFinder; - private final RefreshManager mRefreshManager; private final RefreshListener mRefreshListener = new RefreshListener(); private MessageOrderManager mOrderManager; private final MessageOrderManagerCallback mMessageOrderManagerCallback = @@ -139,34 +123,20 @@ class UIControllerTwoPane implements */ private int mCurrentMailboxUnreadCount; - /** - * 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 mRestoredFragments = new ArrayList(); - - /** - * Whether fragment installation should be hold. - * We hold installing fragments until {@link #installRestoredFragments()} is called. - */ - private boolean mHoldFragmentInstallation = true; - - /** The owner activity */ - private final EmailActivity mActivity; - - private final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker(); - public UIControllerTwoPane(EmailActivity activity) { - mActivity = activity; - mRefreshManager = RefreshManager.getInstance(mActivity); + super(activity); + } + + @Override + public int getLayoutId() { + return R.layout.email_activity_two_pane; } // MailboxFinder$Callback @Override public void onAccountNotFound() { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { - Log.d(Logging.LOG_TAG, "" + this + " onAccountNotFound()"); + Log.d(Logging.LOG_TAG, this + " onAccountNotFound()"); } // Shouldn't happen } @@ -174,7 +144,7 @@ class UIControllerTwoPane implements @Override public void onAccountSecurityHold(long accountId) { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { - Log.d(Logging.LOG_TAG, "" + this + " onAccountSecurityHold()"); + Log.d(Logging.LOG_TAG, this + " onAccountSecurityHold()"); } mActivity.startActivity(AccountSecurity.actionUpdateSecurityIntent(mActivity, accountId, true)); @@ -183,7 +153,7 @@ class UIControllerTwoPane implements @Override public void onMailboxFound(long accountId, long mailboxId) { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { - Log.d(Logging.LOG_TAG, "" + this + " onMailboxFound()"); + Log.d(Logging.LOG_TAG, this + " onMailboxFound()"); } updateMessageList(mailboxId, true, true); } @@ -191,7 +161,7 @@ class UIControllerTwoPane implements @Override public void onMailboxNotFound(long accountId) { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { - Log.d(Logging.LOG_TAG, "" + this + " onMailboxNotFound()"); + Log.d(Logging.LOG_TAG, this + " onMailboxNotFound()"); } // TODO: handle more gracefully. Log.e(Logging.LOG_TAG, "unable to find mailbox for account " + accountId); @@ -411,9 +381,10 @@ class UIControllerTwoPane implements * (Due to the complexity regarding class/activity initialization order, we can't do this in * the constructor.) TODO this should no longer be true when we merge activities. */ + @Override public void onActivityViewReady() { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { - Log.d(Logging.LOG_TAG, "" + this + " onActivityViewReady"); + Log.d(Logging.LOG_TAG, this + " onActivityViewReady"); } mActionBarController = new ActionBarController(mActivity, mActivity.getLoaderManager(), mActivity.getActionBar(), mActionBarControllerCallback); @@ -431,6 +402,7 @@ class UIControllerTwoPane implements * * @see #getActualAccountId() */ + @Override public long getUIAccountId() { return mAccountId; } @@ -441,6 +413,7 @@ class UIControllerTwoPane implements * * @see #getUIAccountId() */ + @Override public long getActualAccountId() { return mAccountId == Account.ACCOUNT_ID_COMBINED_VIEW ? NO_ACCOUNT : mAccountId; } @@ -450,13 +423,29 @@ class UIControllerTwoPane implements * IMPORTANT: Do not confuse this with {@link #mMailboxListMailboxId} which is the id used * for the mailbox list. The two may be different. */ - public long getMessageListMailboxId() { + private long getMessageListMailboxId() { return (mMessageListFragment == null) ? Mailbox.NO_MAILBOX : mMessageListFragment.getMailboxId(); } - public long getMessageId() { + /* + * STOPSHIP Remove this -- see the base class method. + */ + @Override + public long getMailboxSettingsMailboxId() { + return getMessageListMailboxId(); + } + + /* + * STOPSHIP Remove this -- see the base class method. + */ + @Override + public long getSearchMailboxId() { + return getMessageListMailboxId(); + } + + private long getMessageId() { return mMessageId; } @@ -496,85 +485,54 @@ class UIControllerTwoPane implements /** * Called by the host activity at the end of {@link Activity#onCreate}. */ + @Override public void onActivityCreated() { + super.onActivityCreated(); mRefreshManager.registerListener(mRefreshListener); mActionBarController.onActivityCreated(); } - /** - * Install all the fragments kept in {@link #mRestoredFragments}. - * - * Must be called at the end of {@link EmailActivity#onCreate}. - */ - public void installRestoredFragments() { - mHoldFragmentInstallation = false; - - // Install all the fragments restored by the framework. - for (Fragment fragment : mRestoredFragments) { - installFragment(fragment); - } - mRestoredFragments.clear(); - } - - /** - * Called by {@link EmailActivity} when a {@link Fragment} is attached. - * - * 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. - */ - public void onAttachFragment(Fragment fragment) { - if (mHoldFragmentInstallation) { - // Fragment being restored by the framework during the activity recreation. - mRestoredFragments.add(fragment); - return; - } - installFragment(fragment); - } - - /** - * Called from {@link EmailActivity#onStart}. - */ - public void onStart() { + /** {@inheritDoc} */ + @Override + public void onActivityStart() { + super.onActivityStart(); if (isMessageSelected()) { updateMessageOrderManager(); } } - /** - * Called from {@link EmailActivity#onResume}. - */ - public void onResume() { + /** {@inheritDoc} */ + @Override + public void onActivityResume() { + super.onActivityResume(); refreshActionBar(); } - /** - * Called from {@link EmailActivity#onPause}. - */ - public void onPause() { + /** {@inheritDoc} */ + @Override + public void onActivityPause() { + super.onActivityPause(); } - /** - * Called from {@link EmailActivity#onStop}. - */ - public void onStop() { + /** {@inheritDoc} */ + @Override + public void onActivityStop() { stopMessageOrderManager(); + super.onActivityStop(); } - /** - * Called from {@link EmailActivity#onDestroy}. - */ - public void onDestroy() { - mHoldFragmentInstallation = true; // No more fragment installation. - mTaskTracker.cancellAllInterrupt(); + /** {@inheritDoc} */ + @Override + public void onActivityDestroy() { closeMailboxFinder(); mRefreshManager.unregisterListener(mRefreshListener); + super.onActivityDestroy(); } + /** {@inheritDoc} */ + @Override public void onSaveInstanceState(Bundle outState) { - if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { - Log.d(Logging.LOG_TAG, "" + this + " onSaveInstanceState"); - } + super.onSaveInstanceState(outState); outState.putLong(BUNDLE_KEY_ACCOUNT_ID, mAccountId); outState.putLong(BUNDLE_KEY_MAILBOX_ID, mMailboxListMailboxId); outState.putLong(BUNDLE_KEY_MESSAGE_ID, mMessageId); @@ -585,10 +543,10 @@ class UIControllerTwoPane implements } } + /** {@inheritDoc} */ + @Override public void restoreInstanceState(Bundle savedInstanceState) { - if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { - Log.d(Logging.LOG_TAG, "" + this + " restoreInstanceState"); - } + super.restoreInstanceState(savedInstanceState); mAccountId = savedInstanceState.getLong(BUNDLE_KEY_ACCOUNT_ID, NO_ACCOUNT); mMailboxListMailboxId = savedInstanceState.getLong(BUNDLE_KEY_MAILBOX_ID, NO_MAILBOX); mMessageId = savedInstanceState.getLong(BUNDLE_KEY_MESSAGE_ID, NO_MESSAGE); @@ -605,7 +563,9 @@ class UIControllerTwoPane implements // This probably means we need to start MailboxFinder if mMailboxId == -1. } - private void installFragment(Fragment fragment) { + @Override + void installFragment(Fragment fragment) { + super.installFragment(fragment); if (fragment instanceof MailboxListFragment) { mMailboxListFragment = (MailboxListFragment) fragment; mMailboxListFragment.setCallback(this); @@ -647,19 +607,12 @@ class UIControllerTwoPane implements return ft; } - private void commitFragmentTransaction(FragmentTransaction ft) { - ft.commit(); - mActivity.getFragmentManager().executePendingTransactions(); - } - /** - * Show the default view for the account. + * {@inheritDoc} * * On two-pane, it's the account's root mailboxes on the left pane with Inbox on the right pane. - * - * @param accountId ID of the account to load. Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}. - * Must never be {@link #NO_ACCOUNT}. */ + @Override public void openAccount(long accountId) { mMailboxStack.clear(); open(accountId, NO_MAILBOX, NO_MESSAGE); @@ -680,17 +633,12 @@ class UIControllerTwoPane implements } /** - * Loads the given account and optionally selects the given mailbox and message. Used to open - * a particular view at a request from outside of the activity, such as the widget. - * - * @param accountId ID of the account to load. Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}. - * Must never be {@link #NO_ACCOUNT}. - * @param mailboxId ID of the mailbox to load. If {@link #NO_MAILBOX}, load the account's inbox. - * @param messageId ID of the message to load. If {@link #NO_MESSAGE}, do not open a message. + * {@inheritDoc} */ + @Override public void open(long accountId, long mailboxId, long messageId) { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { - Log.d(Logging.LOG_TAG, "" + this + " open accountId=" + accountId + Log.d(Logging.LOG_TAG, this + " open accountId=" + accountId + " mailboxId=" + mailboxId + " messageId=" + messageId); } if (accountId == NO_ACCOUNT) { @@ -726,7 +674,7 @@ class UIControllerTwoPane implements * @throw IllegalStateException if updateXxx methods can't be called in the current state. */ private void preFragmentTransactionCheck() { - if (mHoldFragmentInstallation) { + 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. @@ -754,7 +702,7 @@ class UIControllerTwoPane implements private void updateMailboxList(long accountId, long parentMailboxId, boolean changeVisiblePane, boolean clearDependentPane) { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { - Log.d(Logging.LOG_TAG, "" + this + " updateMailboxList accountId=" + accountId + Log.d(Logging.LOG_TAG, this + " updateMailboxList accountId=" + accountId + " parentMailboxId=" + parentMailboxId); } preFragmentTransactionCheck(); @@ -810,7 +758,7 @@ class UIControllerTwoPane implements private void updateMessageList(long mailboxId, boolean changeVisiblePane, boolean clearDependentPane) { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { - Log.d(Logging.LOG_TAG, "" + this + " updateMessageList mMailboxId=" + mailboxId); + Log.d(Logging.LOG_TAG, this + " updateMessageList mMailboxId=" + mailboxId); } preFragmentTransactionCheck(); if (mailboxId == 0 || mailboxId == -1) { @@ -847,7 +795,7 @@ class UIControllerTwoPane implements */ private void updateMessageView(long messageId) { if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { - Log.d(Logging.LOG_TAG, "" + this + " updateMessageView messageId=" + messageId); + Log.d(Logging.LOG_TAG, this + " updateMessageView messageId=" + messageId); } preFragmentTransactionCheck(); if (messageId == NO_MESSAGE) { @@ -979,14 +927,14 @@ class UIControllerTwoPane implements /** * Handles {@link android.app.Activity#onCreateOptionsMenu} callback. */ + @Override public boolean onCreateOptionsMenu(MenuInflater inflater, Menu menu) { - inflater.inflate(R.menu.message_list_xl_option, menu); + inflater.inflate(R.menu.email_activity_options, menu); return true; } - /** - * Handles {@link android.app.Activity#onPrepareOptionsMenu} callback. - */ + /** {@inheritDoc} */ + @Override public boolean onPrepareOptionsMenu(MenuInflater inflater, Menu menu) { ActivityHelper.updateRefreshMenuIcon(menu.findItem(R.id.refresh), isRefreshEnabled(), @@ -994,11 +942,8 @@ class UIControllerTwoPane implements return true; } - /** - * Handles {@link android.app.Activity#onOptionsItemSelected} callback. - * - * @return true if the option item is handled. - */ + /** {@inheritDoc} */ + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: @@ -1016,12 +961,8 @@ class UIControllerTwoPane implements return false; } - /** - * Performs the back action. - * - * @param isSystemBackKey true if the system back key was pressed. - * true if it's caused by the "home" icon click on the action bar. - */ + /** {@inheritDoc} */ + @Override public boolean onBackPressed(boolean isSystemBackKey) { if (mThreePane.onBackPressed(isSystemBackKey)) { return true; @@ -1059,8 +1000,9 @@ class UIControllerTwoPane implements /** * Handles the "refresh" option item. Opens the settings activity. + * TODO used by experimental code in the activity -- otherwise can be private. */ - // TODO used by experimental code in the activity -- otherwise can be private. + @Override public void onRefresh() { // Cancel previously running instance if any. new RefreshTask(mTaskTracker, mActivity, getActualAccountId(), diff --git a/src/com/android/email/activity/Welcome.java b/src/com/android/email/activity/Welcome.java index a145809d2..927cdb95d 100644 --- a/src/com/android/email/activity/Welcome.java +++ b/src/com/android/email/activity/Welcome.java @@ -25,6 +25,7 @@ import com.android.emailcommon.provider.EmailContent; import com.android.emailcommon.provider.EmailContent.Account; import com.android.emailcommon.provider.EmailContent.Mailbox; import com.android.emailcommon.utility.EmailAsyncTask; +import com.google.common.annotations.VisibleForTesting; import android.app.Activity; import android.content.Context; @@ -77,11 +78,13 @@ public class Welcome extends Activity { private final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker(); /** - * @return true if the two-pane activity should be used on the current configuration. + * @return true if the obsolete phone UI should be used. + * + * STOPSHIP remove this. temporary support for the old activities. */ - public static boolean useTwoPane(Context context) { + public static boolean useOldPhoneActivities(Context context) { final int screenLayout = context.getResources().getConfiguration().screenLayout; - return (screenLayout & Configuration.SCREENLAYOUT_SIZE_XLARGE) != 0; + return (screenLayout & Configuration.SCREENLAYOUT_SIZE_XLARGE) == 0; } /** @@ -190,7 +193,8 @@ public class Welcome extends Activity { * * if {@code account} is -1, open the default account. */ - /* package */ static class MainActivityLauncher extends EmailAsyncTask { + @VisibleForTesting + static class MainActivityLauncher extends EmailAsyncTask { private final Welcome mFromActivity; private final int mDebugPaneMode; private final long mAccountId; @@ -217,7 +221,8 @@ public class Welcome extends Activity { return mMessageId != -1; } - /* package */ static long resolveAccountId(Context context, long accountId, String uuid) { + @VisibleForTesting + static long resolveAccountId(Context context, long accountId, String uuid) { if (!TextUtils.isEmpty(uuid)) { accountId = Account.getAccountIdFromUuid(context, uuid); } @@ -239,19 +244,30 @@ public class Welcome extends Activity { } else { final long accountId = resolveAccountId(mFromActivity, mAccountId, mAccountUuid); - final boolean useTwoPane = (mDebugPaneMode == 2) - || (useTwoPane(mFromActivity) && mDebugPaneMode == 0); + // Use the old phone activities on x-large devices, only when the debug pane mode + // is not specified. + // If the debug pane mode is specified, always use EmailActivity. + // STOPSHIP remove this. temporary support for the old activities. + final boolean useOldPhoneActivities = mDebugPaneMode == 0 + && useOldPhoneActivities(mFromActivity); - if (useTwoPane) { + if (!useOldPhoneActivities) { + final Intent i; if (isMessageSelected()) { - EmailActivity.actionOpenMessage(mFromActivity, accountId, mMailboxId, - mMessageId); + i = EmailActivity.createOpenMessageIntent(mFromActivity, accountId, + mMailboxId, mMessageId); } else if (isMailboxSelected()) { - EmailActivity.actionOpenMailbox(mFromActivity, accountId, mMailboxId); + i = EmailActivity.createOpenMailboxIntent(mFromActivity, accountId, + mMailboxId); } else { - EmailActivity.actionOpenAccount(mFromActivity, accountId); + i = EmailActivity.createOpenAccountIntent(mFromActivity, accountId); } + if (mDebugPaneMode != 0) { + EmailActivity.forcePaneMode(i, mDebugPaneMode == 2); + } + mFromActivity.startActivity(i); } else { + // STOPSHIP remove this. temporary support for the old activities. if (isMessageSelected()) { MessageView.actionView(mFromActivity, mMessageId, mMailboxId); } else if (isMailboxSelected()) {