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
This commit is contained in:
Makoto Onuki 2011-05-09 14:31:11 -07:00
parent 0ea4f9295a
commit f5492ea991
11 changed files with 647 additions and 179 deletions

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<!-- Error message goes over the normal view -->
<TextView
android:id="@+id/error_message"
android:layout_width="match_parent"
android:layout_height="@dimen/error_message_height"
android:paddingLeft="78dip"
android:paddingRight="78dip"
android:gravity="left|center_vertical"
android:textSize="16dip"
android:textColor="#000"
android:singleLine="true"
android:ellipsize="end"
android:background="#ffff66"
/>
<FrameLayout
android:id="@+id/fragment_placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<resources>
<bool name="use_two_pane">true</bool>
</resources>

19
res/values/bools.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<resources>
<bool name="use_two_pane">false</bool>
</resources>

View File

@ -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)

View File

@ -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));

View File

@ -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<Fragment> mRestoredFragments = new ArrayList<Fragment>();
/**
* 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 <code>true</code> if the system back key was pressed.
* <code>false</code> if it's caused by the "home" icon click on the action bar.
*/
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() {
}
}

View File

@ -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;
}
}

View File

@ -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<Fragment> mRestoredFragments = new ArrayList<Fragment>();
/**
* 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 <code>true</code> if the system back key was pressed.
* <code>true</code> 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(),

View File

@ -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<Void, Void, Void> {
@VisibleForTesting
static class MainActivityLauncher extends EmailAsyncTask<Void, Void, Void> {
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()) {