MailboxListFragment: In-place nested mailbox navigation

Now we reuse the same instance of the fragment for nested mailbox
navigation.  (Don't use fragment transaction)

"CursorWithExtras" now only has the child count, so I removed the
bundle version and added a concrete class to MailboxFragmentAdapter.

With this CL the nested mailbox navigation on 1-pane should work, but
not back navigation.  (Back press event isn't connected to the
fragment yet.)

Change-Id: I2c23651d9c8edb5fe062c68bbb9b462c8949ded4
This commit is contained in:
Makoto Onuki 2011-05-31 19:14:11 -07:00
parent 0f84ff2c08
commit 844bf74504
7 changed files with 825 additions and 272 deletions

View File

@ -20,7 +20,6 @@ import com.android.email.Email;
import com.android.email.FolderProperties;
import com.android.email.R;
import com.android.email.data.ClosingMatrixCursor;
import com.android.email.data.CursorWithExtras;
import com.android.email.data.ThrottlingCursorLoader;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent;
@ -34,10 +33,10 @@ import com.android.emailcommon.utility.Utility;
import android.content.Context;
import android.content.Loader;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.database.MergeCursor;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@ -61,6 +60,24 @@ import android.widget.TextView;
/*package*/ class MailboxFragmentAdapter extends MailboxesAdapter {
private static final String MAILBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?" +
" AND " + MailboxColumns.ID + "=?";
/**
* {@link Cursor} with extra information which is returned by the loader created by
* {@link MailboxFragmentAdapter#createMailboxesLoader}.
*/
public static class CursorWithExtras extends CursorWrapper {
/**
* The number of mailboxes in the cursor if the cursor contains top-level mailboxes.
* Otherwise, the number of *child* mailboxes.
*/
public final int mChildCount;
CursorWithExtras(Cursor cursor, int childCount) {
super(cursor);
mChildCount = childCount;
}
}
public MailboxFragmentAdapter(Context context, Callback callback) {
super(context, callback);
}
@ -169,19 +186,32 @@ import android.widget.TextView;
}
/**
* Returns a cursor loader for the mailboxes of the given account. If <code>parentKey</code>
* Returns a cursor loader for the mailboxes of the given account. If <code>parentKey</code>
* refers to a valid mailbox ID [e.g. non-zero], restrict the loader to only those mailboxes
* contained by this parent mailbox.
*
* Note the returned loader always returns a {@link CursorWithExtras}.
*/
public static Loader<Cursor> createLoader(Context context, long accountId, long parentKey) {
public static Loader<Cursor> createMailboxesLoader(Context context, long accountId,
long parentMailboxId) {
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#createLoader accountId=" + accountId);
Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#CursorWithExtras accountId=" + accountId
+ " parentMailboxId=" + parentMailboxId);
}
if (accountId != Account.ACCOUNT_ID_COMBINED_VIEW) {
return new MailboxFragmentLoader(context, accountId, parentKey);
} else {
return new CombinedMailboxLoader(context);
if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
throw new IllegalArgumentException();
}
return new MailboxFragmentLoader(context, accountId, parentMailboxId);
}
/**
* Returns a cursor loader for the combined view.
*/
public static Loader<Cursor> createCombinedViewLoader(Context context) {
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, "MailboxFragmentAdapter#createCombinedViewLoader");
}
return new CombinedMailboxLoader(context);
}
/**
@ -220,6 +250,8 @@ import android.widget.TextView;
/**
* Loads mailboxes that are the children of a given mailbox ID.
*
* The returned {@link Cursor} is always a {@link CursorWithExtras}.
*/
private static class MailboxFragmentLoader extends ThrottlingCursorLoader {
private final Context mContext;
@ -248,25 +280,20 @@ import android.widget.TextView;
@Override
public Cursor loadInBackground() {
boolean parentRemoved = false;
final Cursor childMailboxCursor = super.loadInBackground();
final Cursor returnCursor;
final Bundle extras = new Bundle();
// Setup the extras to return along with the cursor
int cursorCount = childMailboxCursor.getCount();
extras.putInt(CursorWithExtras.EXTRA_MAILBOX_CHILD_COUNT, cursorCount);
long nextParentKey = findParentWithChildren(mParentKey);
extras.putLong(CursorWithExtras.EXTRA_MAILBOX_NEXT_PARENT_ID, nextParentKey);
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mParentKey);
long grandParentKey = mailbox == null ? Mailbox.NO_MAILBOX : mailbox.mParentKey;
extras.putLong(CursorWithExtras.EXTRA_MAILBOX_PARENT_ID, grandParentKey);
final int childCount = childMailboxCursor.getCount();
// If we're not showing the top level mailboxes, add the "parent" mailbox.
if (mParentKey != Mailbox.NO_MAILBOX) {
// If we're not showing the top level mailboxes, add the "parent" mailbox.
final Cursor parentCursor = getContext().getContentResolver().query(
Mailbox.CONTENT_URI, CURMAILBOX_PROJECTION, MAILBOX_SELECTION,
new String[] { Long.toString(mAccountId), Long.toString(mParentKey) },
null);
returnCursor = new MergeCursor(new Cursor[] { parentCursor, childMailboxCursor });
} else {
// Add "Starred", only if the account has at least one starred message.
@ -286,28 +313,8 @@ import android.widget.TextView;
}
}
return new CursorWithExtras(Utility.CloseTraceCursorWrapper.get(returnCursor), extras);
}
/**
* Returns the next parent mailbox with at least one child mailbox. If the given
* mailbox does not exist in the database, returns {@link Mailbox#NO_MAILBOX}. If
* we reach the root parent and we still don't have children, returns
* {@link Mailbox#NO_MAILBOX}.
*/
private long findParentWithChildren(long mailboxId) {
int childCount = Mailbox.count(mContext, Mailbox.CONTENT_URI,
MAILBOX_SELECTION_WITH_PARENT,
new String[] { Long.toString(mAccountId), Long.toString(mailboxId) });
if (childCount == 0 && mailboxId != Mailbox.NO_MAILBOX) {
// There are no children; select parent
Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
if (mailbox == null) {
return Mailbox.NO_MAILBOX;
}
return findParentWithChildren(mailbox.mParentKey);
}
return mailboxId;
return new CursorWithExtras(Utility.CloseTraceCursorWrapper.get(returnCursor),
childCount);
}
}

View File

@ -20,12 +20,13 @@ import com.android.email.Controller;
import com.android.email.Email;
import com.android.email.R;
import com.android.email.RefreshManager;
import com.android.email.data.CursorWithExtras;
import com.android.email.provider.EmailProvider;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent.Account;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.utility.EmailAsyncTask;
import com.android.emailcommon.utility.Utility;
import com.google.common.annotations.VisibleForTesting;
import android.app.Activity;
import android.app.ListFragment;
@ -33,6 +34,7 @@ import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Loader;
import android.content.res.Resources;
import android.database.Cursor;
@ -55,7 +57,90 @@ import java.util.Timer;
import java.util.TimerTask;
/**
* This fragment presents a list of mailboxes for a given account.
* This fragment presents a list of mailboxes for a given account or the combined mailboxes.
*
* This fragment has several parameters that determine the current view.
*
* <pre>
* Parameters:
* - Account ID.
* - Set via {@link #newInstance}.
* - Can be obtained with {@link #getAccountId()}.
* - Will not change throughout fragment lifecycle.
* - Either an actual account ID, or {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
*
* - "Highlight enabled?" flag
* - Set via {@link #newInstance}.
* - Can be obtained with {@link #getEnableHighlight()}.
* - Will not change throughout fragment lifecycle.
* - If {@code true}, we highlight the "selected" mailbox (used only on 2-pane).
* - Note even if it's {@code true}, there may be no highlighted mailbox.
* (This usually happens on 2-pane before the UI controller finds the Inbox to highlight.)
*
* - "Parent" mailbox ID
* - Stored in {@link #mParentMailboxId}
* - Changes as the user navigates through nested mailboxes.
* - Initialized using the {@code mailboxId} parameter for {@link #newInstance}
* in {@link #setInitialParentAndHighlight()}.
*
* - "Highlighted" mailbox
* - Only used when highlighting is enabled. (Otherwise always {@link Mailbox#NO_MAILBOX}.)
* i.e. used only on two-pane.
* - Stored in {@link #mHighlightedMailboxId}
* - Initialized using the {@code mailboxId} parameter for {@link #newInstance}
* in {@link #setInitialParentAndHighlight()}.
*
* - Can be changed any time, using {@link #setHighlightedMailbox(long)}.
*
* - If set, it's considered "selected", and we highlight the list item.
*
* - (It should always be the ID of the list item selected in the list view, but we store it in
* a member for efficiency.)
*
* - Sometimes, we need to set the highlighted mailbox while we're still loading data.
* In this case, we can't update {@link #mHighlightedMailboxId} right away, but need to do so
* in when the next data set arrives, in
* {@link MailboxListFragment.MailboxListLoaderCallbacks#onLoadFinished}. For this, we use
* we store the mailbox ID in {@link #mNextHighlightedMailboxId} and update
* {@link #mHighlightedMailboxId} in onLoadFinished.
*
*
* The "selected" is defined using the "parent" and "highlighted" mailboxes.
* - "Selected" mailbox (also sometimes called "current".)
* - This is what the user thinks it's now selected.
*
* - Can be obtained with {@link #getSelectedMailboxId()}
* - If the "highlighted" mailbox exists, it's the "selected." Otherwise, the "parent"
* is considered "selected."
* - This is what is passed to {@link Callback#onMailboxSelected}.
* </pre>
*
*
* This fragment shows the content in one of the three following views, depending on the
* parameters above.
*
* <pre>
* 1. Combined view
* - Used if the account ID == {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
* - Parent mailbox is always {@link Mailbox#NO_MAILBOX}.
* - List contains:
* - combined mailboxes
* - all accounts
*
* 2. Root view for an account
* - Used if the account ID != {@link Account#ACCOUNT_ID_COMBINED_VIEW} and
* Parent mailbox == {@link Mailbox#NO_MAILBOX}
* - List contains
* - all the top level mailboxes for the selected account.
*
* 3. Root view for a mailbox. (nested view)
* - Used if the account ID != {@link Account#ACCOUNT_ID_COMBINED_VIEW} and
* Parent mailbox != {@link Mailbox#NO_MAILBOX}
* - List contains:
* - parent mailbox (determined by "parent" mailbox ID)
* - all child mailboxes of the parent mailbox.
* </pre>
*
*
* Note that when a fragment is put in the back stack, it'll lose the content view but the fragment
* itself is not destroyed. If you call {@link #getListView()} in this state it'll throw
@ -65,14 +150,20 @@ import java.util.TimerTask;
* no views.
* - Otherwise, make sure to check if the fragment has views with {@link #isViewCreated()}
* before touching any views.
*
* TODO Remove the nested folder navigation code during drag&drop.
*/
public class MailboxListFragment extends ListFragment implements OnItemClickListener,
OnDragListener {
private static final String TAG = "MailboxListFragment";
private static final String BUNDLE_KEY_SELECTED_MAILBOX_ID
private static final String BUNDLE_KEY_PARENT_MAILBOX_ID
= "MailboxListFragment.state.parent_mailbox_id";
private static final String BUNDLE_KEY_HIGHLIGHTED_MAILBOX_ID
= "MailboxListFragment.state.selected_mailbox_id";
private static final String BUNDLE_LIST_STATE = "MailboxListFragment.state.listState";
private static final boolean DEBUG_DRAG_DROP = false; // MUST NOT SUBMIT SET TO TRUE
/** While in drag-n-drop, amount of time before it auto expands; in ms */
private static final long AUTO_EXPAND_DELAY = 750L;
@ -88,7 +179,10 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
/** Argument name(s) */
private static final String ARG_ACCOUNT_ID = "accountId";
private static final String ARG_PARENT_MAILBOX_ID = "parentMailboxId";
private static final String ARG_ENABLE_HIGHLIGHT = "enablehighlight";
private static final String ARG_INITIAL_CURRENT_MAILBOX_ID = "initialParentMailboxId";
private final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker();
/** Timer to auto-expand folder lists during drag-n-drop */
private static final Timer sDragTimer = new Timer();
@ -106,8 +200,14 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
private static Integer sDropTrashColor;
private static Drawable sDropActiveDrawable;
/** ID of the mailbox to hightlight. */
private long mSelectedMailboxId = -1;
// See the class javadoc
private long mParentMailboxId;
private long mHighlightedMailboxId;
/**
* ID of the mailbox that should be highlighted when the next cursor is loaded.
*/
private long mNextHighlightedMailboxId = Mailbox.NO_MAILBOX;
// True if a drag is currently in progress
private boolean mDragInProgress;
@ -147,28 +247,20 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
* @param mailboxId
* The ID of the selected mailbox. This may be real mailbox ID [e.g. a number > 0],
* or a combined mailbox ID [e.g. {@link Mailbox#QUERY_ALL_INBOXES}].
* @param navigate navigate to the mailbox.
* @param nestedNavigation {@code true} if the event is caused by nested mailbox navigation,
* that is, going up or drilling-in to a child mailbox.
*/
public void onMailboxSelected(long accountId, long mailboxId, boolean navigate);
/**
* Called if the mailbox ID is being requested to change. This could occur for several
* reasons; such as if the current, navigated mailbox has no more children.
* @param newMailboxId The new mailbox ID to use for displaying in the mailbox list
* @param selectedMailboxId The new mailbox ID to highlight. If {@link Mailbox#NO_MAILBOX},
* the receiver may select any mailbox it chooses.
*/
public void requestMailboxChange(long newMailboxId, long selectedMailboxId);
/**
* Called when a mailbox is selected during D&D.
*/
public void onMailboxSelectedForDnD(long mailboxId);
public void onMailboxSelected(long accountId, long mailboxId, boolean nestedNavigation);
/** Called when an account is selected on the combined view. */
public void onAccountSelected(long accountId);
/**
* TODO Remove it. The behavior is not well-defined. (Won't get called when highlight is
* disabled.)
* It was added only to update the action bar with the current mailbox name and the
* message count. Remove it and make the action bar watch the mailbox by itself.
*
* Called when the list updates to propagate the current mailbox name and the unread count
* for it.
*
@ -180,17 +272,22 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
* {@link MailboxListFragment#getAccountId()}.
*/
public void onCurrentMailboxUpdated(long mailboxId, String mailboxName, int unreadCount);
/**
* Called when the parent mailbox is changing.
*/
public void onParentMailboxChanged();
}
private static class EmptyCallback implements Callback {
public static final Callback INSTANCE = new EmptyCallback();
@Override public void onMailboxSelected(long accountId, long mailboxId, boolean navigate) {
}
@Override public void onMailboxSelectedForDnD(long mailboxId) { }
@Override public void onMailboxSelected(long accountId, long mailboxId,
boolean nestedNavigation) { }
@Override public void onAccountSelected(long accountId) { }
@Override public void onCurrentMailboxUpdated(long mailboxId, String mailboxName,
int unreadCount) { }
@Override public void requestMailboxChange(long newMailboxId, long selectedMailboxId) { }
@Override
public void onParentMailboxChanged() { }
}
/**
@ -217,17 +314,18 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
* This fragment should be created only with this method. (Arguments should always be set.)
*
* @param accountId The ID of the account we want to view
* @param parentMailboxId The ID of the parent mailbox. Use {@link Mailbox#NO_MAILBOX}
* to open the root.
* @param initialCurrentMailboxId ID of the mailbox of interest.
* Pass {@link Mailbox#NO_MAILBOX} to show top-level mailboxes.
* @param enableHighlight {@code true} if highlighting is enabled on the current screen
* configuration. (We don't highlight mailboxes on one-pane.)
*/
public static MailboxListFragment newInstance(long accountId, long parentMailboxId) {
if (accountId == Account.NO_ACCOUNT) {
throw new IllegalArgumentException();
}
public static MailboxListFragment newInstance(long accountId, long initialCurrentMailboxId,
boolean enableHighlight) {
final MailboxListFragment instance = new MailboxListFragment();
final Bundle args = new Bundle();
args.putLong(ARG_ACCOUNT_ID, accountId);
args.putLong(ARG_PARENT_MAILBOX_ID, parentMailboxId);
args.putLong(ARG_INITIAL_CURRENT_MAILBOX_ID, initialCurrentMailboxId);
args.putBoolean(ARG_ENABLE_HIGHLIGHT, enableHighlight);
instance.setArguments(args);
return instance;
}
@ -239,25 +337,32 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
* constructs, this <em>must</em> be considered immutable.
*/
private Long mImmutableAccountId;
/**
* We will display the children of this mailbox. May be {@link Mailbox#NO_MAILBOX} to display
* all of the top-level mailboxes. Do NOT use directly; instead, use
* {@link #getParentMailboxId()}.
* {@code initialCurrentMailboxId} passed to {@link #newInstance}.
* Do not use directly; instead, use {@link #getInitialCurrentMailboxId()}.
* <p><em>NOTE:</em> Although we cannot force these to be immutable using Java language
* constructs, this <em>must</em> be considered immutable.
*/
private Long mImmutableParentMailboxId;
private long mImmutableInitialCurrentMailboxId;
/**
* {@code enableHighlight} passed to {@link #newInstance}.
* Do not use directly; instead, use {@link #getEnableHighlight()}.
* <p><em>NOTE:</em> Although we cannot force these to be immutable using Java language
* constructs, this <em>must</em> be considered immutable.
*/
private boolean mImmutableEnableHighlight;
private void initializeArgCache() {
if (mImmutableAccountId != null) return;
mImmutableAccountId
= getArguments().getLong(ARG_ACCOUNT_ID, Account.NO_ACCOUNT);
mImmutableParentMailboxId
= getArguments().getLong(ARG_PARENT_MAILBOX_ID, Mailbox.NO_MAILBOX);
mImmutableAccountId = getArguments().getLong(ARG_ACCOUNT_ID);
mImmutableInitialCurrentMailboxId = getArguments().getLong(ARG_INITIAL_CURRENT_MAILBOX_ID);
mImmutableEnableHighlight = getArguments().getBoolean(ARG_ENABLE_HIGHLIGHT);
}
/**
* @return the account ID passed to {@link #newInstance}. Safe to call even before onCreate.
* @return {@code accountId} passed to {@link #newInstance}. Safe to call even before onCreate.
*/
public long getAccountId() {
initializeArgCache();
@ -265,20 +370,21 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
}
/**
* @return the mailbox ID passed to {@link #newInstance}. Safe to call even before onCreate.
* @return {@code initialCurrentMailboxId} passed to {@link #newInstance}.
* Safe to call even before onCreate.
*/
public long getParentMailboxId() {
public long getInitialCurrentMailboxId() {
initializeArgCache();
return mImmutableParentMailboxId;
return mImmutableInitialCurrentMailboxId;
}
/**
* @return true if the top level mailboxes are shown. Safe to call even before onCreate.
* @return {@code enableHighlight} passed to {@link #newInstance}.
* Safe to call even before onCreate.
*/
public boolean isRoot() {
// TODO if we add meta-mailboxes to the database, remove special test for account ID
return getParentMailboxId() == Mailbox.NO_MAILBOX
|| getAccountId() == Account.ACCOUNT_ID_COMBINED_VIEW;
public boolean getEnableHighlight() {
initializeArgCache();
return mImmutableEnableHighlight;
}
@Override
@ -304,9 +410,13 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
mRefreshManager = RefreshManager.getInstance(mActivity);
mListAdapter = new MailboxFragmentAdapter(mActivity, mMailboxesAdapterCallback);
setListAdapter(mListAdapter); // It's safe to do even before the list view is created.
if (savedInstanceState != null) {
if (savedInstanceState == null) {
setInitialParentAndHighlight();
} else {
restoreInstanceState(savedInstanceState);
}
if (sDropTrashColor == null) {
Resources res = getResources();
sDropTrashColor = res.getColor(R.color.mailbox_drop_destructive_bg_color);
@ -314,6 +424,24 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
}
}
/**
* Set {@link #mParentMailboxId} and {@link #mHighlightedMailboxId} from the fragment arguments.
*/
private void setInitialParentAndHighlight() {
if (getAccountId() == Account.ACCOUNT_ID_COMBINED_VIEW) {
// For the combined view, always show the top-level, but highlight the "current".
mParentMailboxId = Mailbox.NO_MAILBOX;
} else {
// Otherwise, try using the "current" as the "parent" (and also highlight it).
// If it has no children, we go up in onLoadFinished().
mParentMailboxId = getInitialCurrentMailboxId();
}
// Highlight the mailbox of interest
if (getEnableHighlight()) {
mHighlightedMailboxId = getInitialCurrentMailboxId();
}
}
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@ -345,7 +473,7 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
lv.setOnDragListener(this);
startLoading();
startLoading(mParentMailboxId, mHighlightedMailboxId);
}
public void setCallback(Callback callback) {
@ -368,15 +496,6 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
return false;
}
/**
* Sets the selected mailbox to the given ID. Sub-folders will not be loaded.
* @param mailboxId The ID of the mailbox to select.
*/
public void setSelectedMailbox(long mailboxId) {
mSelectedMailboxId = mailboxId;
highlightSelectedMailbox(true);
}
/**
* Called when the Fragment is visible to the user.
*/
@ -442,6 +561,7 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, this + " onDestroy");
}
mTaskTracker.cancellAllInterrupt();
super.onDestroy();
}
@ -459,7 +579,8 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
Log.d(Logging.LOG_TAG, this + " onSaveInstanceState");
}
super.onSaveInstanceState(outState);
outState.putLong(BUNDLE_KEY_SELECTED_MAILBOX_ID, mSelectedMailboxId);
outState.putLong(BUNDLE_KEY_PARENT_MAILBOX_ID, mParentMailboxId);
outState.putLong(BUNDLE_KEY_HIGHLIGHTED_MAILBOX_ID, mHighlightedMailboxId);
if (isViewCreated()) {
outState.putParcelable(BUNDLE_LIST_STATE, getListView().onSaveInstanceState());
}
@ -469,23 +590,197 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, this + " restoreInstanceState");
}
mSelectedMailboxId = savedInstanceState.getLong(BUNDLE_KEY_SELECTED_MAILBOX_ID);
mParentMailboxId = savedInstanceState.getLong(BUNDLE_KEY_PARENT_MAILBOX_ID);
mHighlightedMailboxId = savedInstanceState.getLong(BUNDLE_KEY_HIGHLIGHTED_MAILBOX_ID);
mSavedListState = savedInstanceState.getParcelable(BUNDLE_LIST_STATE);
}
private void startLoading() {
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, this + " startLoading");
/**
* @return "Selected" mailbox ID.
*/
public long getSelectedMailboxId() {
return (mHighlightedMailboxId != Mailbox.NO_MAILBOX) ? mHighlightedMailboxId
: mParentMailboxId;
}
/**
* @return {@code true} if top-level mailboxes are shown. {@code false} otherwise.
*/
public boolean isRoot() {
return mParentMailboxId == Mailbox.NO_MAILBOX;
}
/**
* Navigate one level up in the mailbox hierarchy. Does nothing if at the root account view.
*/
public boolean navigateUp() {
if (isRoot()) {
return false;
}
FindParentMailboxTask.ResultCallback callback = new FindParentMailboxTask.ResultCallback() {
@Override public void onResult(long nextParentMailboxId,
long nextHighlightedMailboxId, long nextSelectedMailboxId) {
startLoading(nextParentMailboxId, nextHighlightedMailboxId);
if (nextSelectedMailboxId != Mailbox.NO_MAILBOX) {
mCallback.onMailboxSelected(getAccountId(), nextSelectedMailboxId, true);
}
}
};
new FindParentMailboxTask(
getActivity().getApplicationContext(), mTaskTracker, getAccountId(),
getEnableHighlight(), mParentMailboxId, mHighlightedMailboxId, callback
).cancelPreviousAndExecuteParallel((Void[]) null);
return true;
}
/**
* A task to determine what parent mailbox ID/highlighted mailbox ID to use for the "UP"
* navigation, given the current parent mailbox ID, the highlighted mailbox ID, and {@link
* #mEnableHighlight}.
*/
@VisibleForTesting
static class FindParentMailboxTask extends EmailAsyncTask<Void, Void, Long[]> {
public interface ResultCallback {
/**
* Callback to get the result.
*
* @param nextParentMailboxId ID of the mailbox to use
* @param nextHighlightedMailboxId ID of the mailbox to highlight
* @param nextSelectedMailboxId ID of the mailbox to notify with
* {@link Callback#onMailboxSelected}.
*/
public void onResult(long nextParentMailboxId, long nextHighlightedMailboxId,
long nextSelectedMailboxId);
}
private final Context mContext;
private final long mAccountId;
private final boolean mEnableHighlight;
private final long mParentMailboxId;
private final long mHighlightedMailboxId;
private final ResultCallback mCallback;
public FindParentMailboxTask(Context context, EmailAsyncTask.Tracker taskTracker,
long accountId, boolean enableHighlight, long parentMailboxId,
long highlightedMailboxId, ResultCallback callback) {
super(taskTracker);
mContext = context;
mAccountId = accountId;
mEnableHighlight = enableHighlight;
mParentMailboxId = parentMailboxId;
mHighlightedMailboxId = highlightedMailboxId;
mCallback = callback;
}
@Override
protected Long[] doInBackground(Void... params) {
Mailbox parentMailbox = Mailbox.restoreMailboxWithId(mContext, mParentMailboxId);
final long nextParentId = (parentMailbox == null) ? Mailbox.NO_MAILBOX
: parentMailbox.mParentKey;
final long nextHighlightedId;
final long nextSelectedId;
if (mEnableHighlight) {
// If the "parent" is highlighted before the transition, it should still be
// highlighted after the upper level view.
if (mParentMailboxId == mHighlightedMailboxId) {
nextHighlightedId = mParentMailboxId;
} else {
// Otherwise, the next parent will be highlighted, unless we're going up to
// the root, in which case Inbox should be highlighted.
if (nextParentId == Mailbox.NO_MAILBOX) {
nextHighlightedId = Mailbox.findMailboxOfType(mContext, mAccountId,
Mailbox.TYPE_INBOX);
} else {
nextHighlightedId = nextParentId;
}
}
// Highlighted one will be "selected".
nextSelectedId = nextHighlightedId;
} else { // !mEnableHighlight
nextHighlightedId = Mailbox.NO_MAILBOX;
// Parent will be selected.
nextSelectedId = nextParentId;
}
return new Long[]{nextParentId, nextHighlightedId, nextSelectedId};
}
@Override
protected void onPostExecute(Long[] result) {
mCallback.onResult(result[0], result[1], result[2]);
}
}
/**
* Starts the loader.
*
* @param parentMailboxId Mailbox ID to be used as the "parent" mailbox
* @param highlightedMailboxId Mailbox ID that should be highlighted when the data is loaded.
*/
private void startLoading(long parentMailboxId, long highlightedMailboxId
) {
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, this + " startLoading parent=" + parentMailboxId
+ " highlighted=" + highlightedMailboxId);
}
final LoaderManager lm = getLoaderManager();
boolean parentMailboxChanging = false;
// Parent mailbox changing -- destroy the current loader to force reload.
if (mParentMailboxId != parentMailboxId) {
lm.destroyLoader(MAILBOX_LOADER_ID);
setListShown(false);
parentMailboxChanging = true;
}
mParentMailboxId = parentMailboxId;
if (getEnableHighlight()) {
mNextHighlightedMailboxId = highlightedMailboxId;
}
lm.initLoader(MAILBOX_LOADER_ID, null, new MailboxListLoaderCallbacks());
if (parentMailboxChanging) {
mCallback.onParentMailboxChanged();
}
}
/**
* Highlight the given mailbox.
*
* If data is already loaded, it just sets {@link #mHighlightedMailboxId} and highlight the
* corresponding list item. (And if the corresponding list item is not found,
* {@link #mHighlightedMailboxId} is set to {@link Mailbox#NO_MAILBOX})
*
* If we're still loading data, it sets {@link #mNextHighlightedMailboxId} instead, and then
* it'll be set to {@link #mHighlightedMailboxId} in
* {@link MailboxListLoaderCallbacks#onLoadFinished}.
*
* @param mailboxId The ID of the mailbox to highlight.
*/
public void setHighlightedMailbox(long mailboxId) {
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, this + " setHighlightedMailbox mailbox=" + mailboxId);
}
if (!getEnableHighlight()) {
return;
}
if (mListAdapter.getCursor() == null) {
// List not loaded yet. Just remember the ID here and let onLoadFinished() update
// mHighlightedMailboxId.
mNextHighlightedMailboxId = mailboxId;
return;
}
mHighlightedMailboxId = mailboxId;
updateHighlightedMailbox(true);
}
// TODO This class probably should be made static. There are many calls into the enclosing
// class and we need to be cautious about what we call while in these callbacks
private class MailboxListLoaderCallbacks implements LoaderCallbacks<Cursor> {
/** Whether or not the loader has finished at least once */
private boolean mIsFirstLoad;
@Override
@ -494,8 +789,12 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
Log.d(Logging.LOG_TAG, MailboxListFragment.this + " onCreateLoader");
}
mIsFirstLoad = true;
return MailboxFragmentAdapter.createLoader(getActivity(), getAccountId(),
getParentMailboxId());
if (getAccountId() == Account.ACCOUNT_ID_COMBINED_VIEW) {
return MailboxFragmentAdapter.createCombinedViewLoader(getActivity());
} else {
return MailboxFragmentAdapter.createMailboxesLoader(getActivity(), getAccountId(),
mParentMailboxId);
}
}
@Override
@ -504,44 +803,43 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
Log.d(Logging.LOG_TAG, MailboxListFragment.this + " onLoadFinished count="
+ cursor.getCount());
}
// Note at this point we can assume the view is created.
// Note in onLoadFinished we can assume the view is created.
// The loader manager doesn't deliver results when a fragment is stopped.
// Validate the cursor and make sure we're showing the "right thing"
if (cursor instanceof CursorWithExtras) {
CursorWithExtras c = (CursorWithExtras) cursor;
int childCount = c.getInt(CursorWithExtras.EXTRA_MAILBOX_CHILD_COUNT, -1);
if (childCount == 0) {
long nextParentId = c.getLong(CursorWithExtras.EXTRA_MAILBOX_NEXT_PARENT_ID);
long grandParentId = c.getLong(CursorWithExtras.EXTRA_MAILBOX_PARENT_ID);
long highlightId;
// Only set a mailbox highlight if we're choosing our immediate parent
if (grandParentId == nextParentId) {
highlightId = getParentMailboxId();
} else {
highlightId = Mailbox.NO_MAILBOX;
}
// If the next parent w/ children isn't us, request a change
if (nextParentId != getParentMailboxId()) {
mCallback.requestMailboxChange(nextParentId, highlightId);
return;
}
// If we're showing a nested mailboxes, and the current parent mailbox has no children,
// go up.
if (getAccountId() != Account.ACCOUNT_ID_COMBINED_VIEW) {
MailboxFragmentAdapter.CursorWithExtras c =
(MailboxFragmentAdapter.CursorWithExtras) cursor;
if ((c.mChildCount == 0) && !isRoot()) {
navigateUp();
return;
}
}
if (cursor.getCount() == 0) {
// If there's no row, don't set it to the ListView.
// Instead use setListShown(false) to make ListFragment show progress icon.
// There's no row -- call setListShown(false) to make ListFragment show progress
// icon.
mListAdapter.swapCursor(null);
setListShown(false);
} else {
// Set the adapter.
mListAdapter.swapCursor(cursor);
setListShown(true);
// Update the highlighted mailbox
if (mNextHighlightedMailboxId != Mailbox.NO_MAILBOX) {
mHighlightedMailboxId = mNextHighlightedMailboxId;
mNextHighlightedMailboxId = Mailbox.NO_MAILBOX;
}
// We want to make visible the selection only for the first load.
// Re-load caused by content changed events shouldn't scroll the list.
highlightSelectedMailbox(mIsFirstLoad);
if (!updateHighlightedMailbox(mIsFirstLoad)) {
// TODO We should just select the parent mailbox, or Inbox if it's already
// top-level. Make sure to call onMailboxSelected().
return;
}
}
// List has been reloaded; clear any drop target information
@ -580,33 +878,50 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
if (mListAdapter.isAccountRow(position)) {
mCallback.onAccountSelected(id);
} else {
// STOPSHIP On phone, we need a way to open a message list without navigating to the
// mailbox.
mCallback.onMailboxSelected(mListAdapter.getAccountId(position), id,
isNavigable(id));
// Save account-id. (Need to do this before startLoading() below, which will destroy
// the current loader and make the mListAdapter lose the cursor.
// Note, don't just use getAccountId(). A mailbox may tied to a different account ID
// from getAccountId(). (Currently "Starred" does so.)
final long accountId = mListAdapter.getAccountId(position);
boolean nestedNavigation = false;
if (isNavigable(id) && (id != mParentMailboxId)) {
// Drill-in. Selected one will be the next parent, and it'll also be highlighted.
startLoading(id, id);
nestedNavigation = true;
}
mCallback.onMailboxSelected(accountId, id, nestedNavigation);
}
}
/**
* Highlight the selected mailbox.
* Really highlight the mailbox for {@link #mHighlightedMailboxId} on the list view.
*
* Note if a list item for {@link #mHighlightedMailboxId} is not found,
* {@link #mHighlightedMailboxId} will be set to {@link Mailbox#NO_MAILBOX}.
*
* @return false when the highlighted mailbox seems to be gone; i.e. if
* {@link #mHighlightedMailboxId} is set but not found in the list.
*/
private void highlightSelectedMailbox(boolean ensureSelectionVisible) {
if (!isViewCreated()) {
return; // Nothing to highlight
private boolean updateHighlightedMailbox(boolean ensureSelectionVisible) {
if (!getEnableHighlight() || !isViewCreated()) {
return true; // Nothing to highlight
}
final ListView lv = getListView();
boolean found = false;
String mailboxName = "";
int unreadCount = 0;
if (mSelectedMailboxId == -1) {
if (mHighlightedMailboxId == Mailbox.NO_MAILBOX) {
// No mailbox selected
lv.clearChoices();
found = true;
} else {
// TODO Don't mix list view & list adapter indices. This is a recipe for disaster.
final int count = lv.getCount();
for (int i = 0; i < count; i++) {
if (mListAdapter.getId(i) != mSelectedMailboxId) {
if (mListAdapter.getId(i) != mHighlightedMailboxId) {
continue;
}
found = true;
lv.setItemChecked(i, true);
if (ensureSelectionVisible) {
Utility.listViewSmoothScrollToPosition(getActivity(), lv, i);
@ -616,7 +931,12 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
break;
}
}
mCallback.onCurrentMailboxUpdated(mSelectedMailboxId, mailboxName, unreadCount);
if (found) {
mCallback.onCurrentMailboxUpdated(mHighlightedMailboxId, mailboxName, unreadCount);
} else {
mHighlightedMailboxId = Mailbox.NO_MAILBOX;
}
return found;
}
// Drag & Drop handling
@ -659,7 +979,6 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
@Override
public void run() {
stopDragTimer();
mCallback.onMailboxSelectedForDnD(newTarget.mMailboxId);
}
});
}

View File

@ -74,17 +74,16 @@ class UIControllerOnePane extends UIControllerBase {
}
@Override
public void onMailboxSelected(long accountId, long mailboxId, boolean navigate) {
public void onMailboxSelected(long accountId, long mailboxId, boolean nestedNavigation) {
if (nestedNavigation) {
return; // Nothing to do on 1-pane.
}
openMailbox(accountId, mailboxId);
}
@Override
public void onMailboxSelectedForDnD(long mailboxId) {
// No drag&drop on 1-pane
}
@Override
public void requestMailboxChange(long newMailboxId, long selectedMailboxId) {
public void onParentMailboxChanged() {
refreshActionBar();
}
};
@ -98,7 +97,6 @@ class UIControllerOnePane extends UIControllerBase {
@Override
public void onEnterSelectionMode(boolean enter) {
// TODO Auto-generated method stub
}
@Override
@ -345,6 +343,8 @@ class UIControllerOnePane extends UIControllerBase {
open(getUIAccountId(), Mailbox.NO_MAILBOX, Message.NO_MESSAGE);
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();
@ -402,7 +402,7 @@ class UIControllerOnePane extends UIControllerBase {
} else {
ft.replace(R.id.fragment_placeholder,
MailboxListFragment.newInstance(accountId, Mailbox.NO_MAILBOX));
MailboxListFragment.newInstance(accountId, Mailbox.NO_MAILBOX, false));
}
mCurrentAccountId = accountId;

View File

@ -169,48 +169,15 @@ class UIControllerTwoPane extends UIControllerBase implements
// MailboxListFragment$Callback
@Override
public void onMailboxSelected(long accountId, long mailboxId, boolean navigate) {
public void onMailboxSelected(long accountId, long mailboxId, boolean nestedNavigation) {
if ((accountId == Account.NO_ACCOUNT) || (mailboxId == Mailbox.NO_MAILBOX)) {
throw new IllegalArgumentException(); // Shouldn't happen.
throw new IllegalArgumentException();
}
if (navigate) {
if (mailboxId != getMailboxListMailboxId()) {
// Don't navigate to the same mailbox id twice in a row
openMailbox(accountId, mailboxId);
}
} else {
// Regular case -- just open the mailbox on the message list.
if (getMessageListMailboxId() != mailboxId) {
updateMessageList(accountId, mailboxId, true);
}
}
@Override
public void onMailboxSelectedForDnD(long mailboxId) {
// STOPSHIP the new mailbox list created here doesn't know D&D is in progress. b/4332725
updateMailboxList(getUIAccountId(), mailboxId,
false /* don't clear message list and message view */);
}
@Override
public void requestMailboxChange(final long newMailboxId, final long selectedMailboxId) {
// Unfortunately if the screen rotates while the task is running, we just cancel the task
// so mailbox change request will be gone. But we'll live with it as it's not too critical.
new EmailAsyncTask<Void, Void, Void>(mTaskTracker) {
@Override protected Void doInBackground(Void... params) { return null; }
@Override protected void onPostExecute(Void mailboxId) {
if (selectedMailboxId == getMailboxListMailboxId()) {
// We're not changing selections; just the contents of the mailbox list
updateMailboxList(getActualAccountId(), newMailboxId, false);
mMailboxListFragment.setSelectedMailbox(selectedMailboxId);
} else {
// Select a whole new mailbox
openMailbox(getActualAccountId(), newMailboxId);
}
}
}.cancelPreviousAndExecuteSerial();
}
// MailboxListFragment$Callback
@Override
public void onAccountSelected(long accountId) {
@ -225,6 +192,12 @@ class UIControllerTwoPane extends UIControllerBase implements
refreshActionBar();
}
// MailboxListFragment$Callback
@Override
public void onParentMailboxChanged() {
refreshActionBar();
}
// MessageListFragment$Callback
@Override
public void onMessageOpen(long messageId, long messageMailboxId, long listMailboxId,
@ -232,8 +205,10 @@ class UIControllerTwoPane extends UIControllerBase implements
if (type == MessageListFragment.Callback.TYPE_DRAFT) {
MessageCompose.actionEditDraft(mActivity, messageId);
} else {
updateMessageView(messageId);
mThreePane.showRightPane();
if (getMessageId() != messageId) {
updateMessageView(messageId);
mThreePane.showRightPane();
}
}
}
@ -431,7 +406,7 @@ class UIControllerTwoPane extends UIControllerBase implements
* {@link #getMessageListMailboxId()}
*/
private long getMailboxListMailboxId() {
return isMailboxListInstalled() ? mMailboxListFragment.getParentMailboxId()
return isMailboxListInstalled() ? mMailboxListFragment.getSelectedMailboxId()
: Mailbox.NO_MAILBOX;
}
@ -634,13 +609,11 @@ class UIControllerTwoPane extends UIControllerBase implements
}
mThreePane.showLeftPane();
} else if (messageId == Message.NO_MESSAGE) {
// STOPSHIP Use the appropriate parent mailbox ID
updateMailboxList(accountId, mailboxId, true);
updateMessageList(accountId, mailboxId, true);
mThreePane.showLeftPane();
} else {
// STOPSHIP Use the appropriate parent mailbox ID
updateMailboxList(accountId, mailboxId, true);
updateMessageList(accountId, mailboxId, true);
updateMessageView(messageId);
@ -669,15 +642,15 @@ class UIControllerTwoPane extends UIControllerBase implements
* <code>forceReload</code> is <code>true</code>.
*
* @param accountId ID of the account to load. Must never be {@link Account#NO_ACCOUNT}.
* @param parentMailboxId ID of the mailbox to use as the parent mailbox. Pass
* @param mailboxId ID of the mailbox to use as the "selected". Pass
* {@link Mailbox#NO_MAILBOX} to show the root mailboxes.
* @param clearDependentPane if true, the message list and the message view will be cleared
*/
private void updateMailboxList(long accountId, long parentMailboxId,
private void updateMailboxList(long accountId, long mailboxId,
boolean clearDependentPane) {
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, this + " updateMailboxList accountId=" + accountId
+ " parentMailboxId=" + parentMailboxId);
+ " mailboxId=" + mailboxId);
}
preFragmentTransactionCheck();
if (accountId == Account.NO_ACCOUNT) {
@ -686,11 +659,11 @@ class UIControllerTwoPane extends UIControllerBase implements
final FragmentManager fm = mActivity.getFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
if ((getUIAccountId() != accountId)
|| (getMailboxListMailboxId() != parentMailboxId)) {
if ((getUIAccountId() != accountId) || (getMailboxListMailboxId() != mailboxId)) {
uninstallMailboxListFragment(ft);
ft.add(mThreePane.getLeftPaneId(),
MailboxListFragment.newInstance(accountId, parentMailboxId));
MailboxListFragment.newInstance(accountId, mailboxId, true));
}
if (clearDependentPane) {
uninstallMessageListFragment(ft);
@ -744,7 +717,7 @@ class UIControllerTwoPane extends UIControllerBase implements
}
commitFragmentTransaction(ft);
mMailboxListFragment.setSelectedMailbox(mailboxId);
mMailboxListFragment.setHighlightedMailbox(mailboxId);
// Update action bar / menu
updateRefreshProgress();
@ -896,20 +869,12 @@ class UIControllerTwoPane extends UIControllerBase implements
public boolean onBackPressed(boolean isSystemBackKey) {
if (mThreePane.onBackPressed(isSystemBackKey)) {
return true;
} else if (navigateToParentMailboxList()) {
} else if (isMailboxListInstalled() && mMailboxListFragment.navigateUp()) {
return true;
}
return false;
}
private boolean navigateToParentMailboxList() {
if (!isMailboxListInstalled() || mMailboxListFragment.isRoot()) {
return false;
}
super.navigateToParentMailboxList(mMailboxListFragment.getParentMailboxId());
return true;
}
/**
* Handles the "refresh" option item. Opens the settings activity.
* TODO used by experimental code in the activity -- otherwise can be private.
@ -1073,7 +1038,8 @@ class UIControllerTwoPane extends UIControllerBase implements
public boolean shouldShowUp() {
final int visiblePanes = mThreePane.getVisiblePanes();
final boolean leftPaneHidden = ((visiblePanes & ThreePaneLayout.PANE_LEFT) == 0);
return leftPaneHidden || (isMailboxListInstalled() && !mMailboxListFragment.isRoot());
return leftPaneHidden
|| (isMailboxListInstalled() && !mMailboxListFragment.isRoot());
}
}
}

View File

@ -64,8 +64,12 @@ public class Welcome extends Activity {
* Open a message (account id=1, mailbox id=2, message id=3)
adb shell am start -a android.intent.action.MAIN \
-d '"content://ui.email.android.com/view/mailbox?ACCOUNT_ID=1&MAILBOX_ID=2&MESSAGE_ID=3"' \
-e DEBUG_PANE_MODE 2 \
-e DEBUG_PANE_MODE 2
* Open the combined starred on the combined view
adb shell am start -a android.intent.action.MAIN \
-d '"content://ui.email.android.com/view/mailbox?ACCOUNT_ID=1152921504606846976&MAILBOX_ID=-4"' \
-e DEBUG_PANE_MODE 2
*/
/**

View File

@ -1,58 +0,0 @@
/*
* 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.data;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.os.Bundle;
/**
* A special cursor that contains additional data.
*
* <p>TODO Use this instead of EmailWidgetLoader$CursorWithCounts.
*/
public class CursorWithExtras extends CursorWrapper {
/** The number of children in a mailbox. If set, must be a positive value. */
public final static String EXTRA_MAILBOX_CHILD_COUNT = "mailboxChildCount";
/** The ID of the next, immediate parent mailbox */
public final static String EXTRA_MAILBOX_PARENT_ID = "mailboxParentId";
/** The ID of the next mailbox in the hierarchy with at least one child */
public final static String EXTRA_MAILBOX_NEXT_PARENT_ID = "mailboxNextParentId";
private final Bundle mExtras;
public CursorWithExtras(Cursor cursor, Bundle extras) {
super(cursor);
mExtras = extras;
}
public int getInt(String key) {
return getInt(key, 0);
}
public int getInt(String key, int defaultValue) {
return mExtras == null ? defaultValue : mExtras.getInt(key);
}
public long getLong(String key) {
return getLong(key, 0L);
}
public long getLong(String key, long defaultValue) {
return mExtras == null ? defaultValue : mExtras.getLong(key);
}
}

View File

@ -0,0 +1,315 @@
/*
* 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.DBTestHelper;
import com.android.email.provider.ProviderTestUtils;
import com.android.emailcommon.provider.EmailContent.Account;
import com.android.emailcommon.provider.Mailbox;
import android.content.Context;
import android.test.AndroidTestCase;
/**
* Unit tests for {@link MailboxListFragment.FindParentMailboxTask}.
*/
public class FindParentMailboxTaskTest extends AndroidTestCase {
private Context mProviderContext;
/** ID of the account created by {@link #setUpMailboxes}. */
private long mAccountId;
/**
* IDs for the mailboxes created by {@link #setUpMailboxes}.
*
* Mailbox hierarchy:
* <pre>
* |-Inbox
* |-Parent
* |-Child1
* |-Child2
* |-GrandChild1
* |-GrandChild2
* </pre>
*/
private long mIdInbox;
private long mIdParent;
private long mIdChild1;
private long mIdChild2;
private long mIdGrandChild1;
private long mIdGrandChild2;
@Override
protected void setUp() throws Exception {
super.setUp();
mProviderContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext(
getContext());
setUpMailboxes();
}
/**
* Set up a test account and mailboxes.
*/
private void setUpMailboxes() {
Account a = ProviderTestUtils.setupAccount("a", true, mProviderContext);
mAccountId = a.mId;
mIdInbox = createMailboxAndGetId("Inbox", a, Mailbox.TYPE_INBOX, Mailbox.NO_MAILBOX);
mIdParent = createMailboxAndGetId("P", a, Mailbox.TYPE_MAIL, Mailbox.NO_MAILBOX);
mIdChild1 = createMailboxAndGetId("C1", a, Mailbox.TYPE_MAIL, mIdParent);
mIdChild2 = createMailboxAndGetId("C2", a, Mailbox.TYPE_MAIL, mIdParent);
mIdGrandChild1 = createMailboxAndGetId("G1", a, Mailbox.TYPE_MAIL, mIdChild2);
mIdGrandChild2 = createMailboxAndGetId("G2", a, Mailbox.TYPE_MAIL, mIdChild2);
}
private long createMailboxAndGetId(String name, Account account, int type,
long parentMailboxId) {
Mailbox m = ProviderTestUtils.setupMailbox(name, account.mId, false, mProviderContext,
type);
m.mParentKey = parentMailboxId;
m.save(mProviderContext);
return m.mId;
}
/**
* Tests for two-pane. (highlighting is enabled)
*/
public void testWithHighlight() {
/*
* In the comments below, [MAILBOX] indicates "highlighted", and MAILBOX* indicates
* "selected".
*/
/*
* from:
* - [Child2]
* - GChild1
* - GChild2
*
* to:
* - Parent
* - Child1
* - [Child2]*
*/
doCheckWithHighlight(
mIdChild2, // Current parent
mIdChild2, // Current highlighted
mIdParent, // Next root
mIdChild2, // Next highlighted
mIdChild2 // Next selected
);
/*
* from:
* - Child2
* - [GChild1]
* - GChild2
*
* to:
* - [Parent]*
* - Child1
* - Child2
*/
doCheckWithHighlight(
mIdChild2, // Current parent
mIdGrandChild1, // Current highlighted
mIdParent, // Next root
mIdParent, // Next highlighted
mIdParent // Next selected
);
/*
* from:
* - [Parent]
* - Child1
* - Child2
*
* to:
* - Inbox
* - [Parent]*
*/
doCheckWithHighlight(
mIdParent, // Current parent
mIdParent, // Current highlighted
Mailbox.NO_MAILBOX, // Next root
mIdParent, // Next highlighted
mIdParent // Next selected
);
/*
* from:
* - Parent
* - [Child1]
* - Child2
*
* to:
* - [Inbox]*
* - Parent
*/
doCheckWithHighlight(
mIdParent, // Current parent
mIdChild1, // Current highlighted
Mailbox.NO_MAILBOX, // Next root
mIdInbox, // Next highlighted
mIdInbox // Next selected
);
/*
* Special case.
* Up from root view, with "Parent" highlighted. "Up" will be disabled in this case, but
* if we were to run the task, it'd work as if the current parent mailbox is gone.
* i.e. just show the top level mailboxes, with Inbox highlighted.
*
* from:
* - Inbox
* - [Parent]
*
* to:
* - [Inbox]
* - Parent
*/
doCheckWithHighlight(
Mailbox.NO_MAILBOX, // Current parent
mIdParent, // Current highlighted
Mailbox.NO_MAILBOX, // Next root
mIdInbox, // Next highlighted
mIdInbox // Next selected
);
/*
* Special case.
* Current parent mailbox is gone. The result should be same as the above.
*
* from:
* (current mailbox just removed)
*
* to:
* - [Inbox]
* - Parent
*/
doCheckWithHighlight(
12312234234L, // Current parent
mIdParent, // Current highlighted
Mailbox.NO_MAILBOX, // Next root
mIdInbox, // Next highlighted
mIdInbox // Next selected
);
}
private void doCheckWithHighlight(
long parentMailboxId, long highlightedMailboxId,
long expectedNextParent, long expectedNextHighlighted, long expectedNextSelected) {
doCheck(true, parentMailboxId, highlightedMailboxId,
expectedNextParent, expectedNextHighlighted, expectedNextSelected);
}
/**
* Tests for one-pane. (highlighting is disable)
*/
public void testWithNoHighlight() {
/*
* from:
* - Child2
* - GChild1
* - GChild2
*
* to:
* - Parent
* - Child1
* - Child2
*/
doCheckWithNoHighlight(
mIdChild2, // Current parent
mIdParent // Next root
);
/*
* from:
* - Parent
* - Child1
* - Child2
*
* to:
* - Inbox
* - Parent
*/
doCheckWithNoHighlight(
mIdParent, // Current parent
Mailbox.NO_MAILBOX // Next root
);
/*
* Special case.
* Current parent mailbox is gone. The top-level mailboxes should be shown.
*
* from:
* (current mailbox just removed)
*
* to:
* - Inbox
* - Parent
*/
doCheckWithNoHighlight(
12312234234L, // Current parent
Mailbox.NO_MAILBOX // Next root
);
}
private void doCheckWithNoHighlight(long parentMailboxId, long expectedNextParent) {
doCheck(false, parentMailboxId, Mailbox.NO_MAILBOX,
expectedNextParent, Mailbox.NO_MAILBOX,
expectedNextParent /* parent should always be selected */);
}
private void doCheck(boolean enableHighlight,
long parentMailboxId, long highlightedMailboxId,
long expectedNextParent, long expectedNextHighlighted, long expectedNextSelected) {
ResultCallback result = new ResultCallback();
MailboxListFragment.FindParentMailboxTask task
= new MailboxListFragment.FindParentMailboxTask(
mProviderContext, null, mAccountId, enableHighlight, parentMailboxId,
highlightedMailboxId, result);
// Can't execute an async task on the test thread, so emulate execution...
task.onPostExecute(task.doInBackground((Void[]) null));
assertEquals("parent", expectedNextParent, result.mNextParentMailboxId);
assertEquals("highlighted", expectedNextHighlighted, result.mNextHighlightedMailboxId);
assertEquals("selected", expectedNextSelected, result.mNextSelectedMailboxId);
}
private static class ResultCallback
implements MailboxListFragment.FindParentMailboxTask.ResultCallback {
public long mNextParentMailboxId;
public long mNextHighlightedMailboxId;
public long mNextSelectedMailboxId;
@Override
public void onResult(long nextParentMailboxId, long nextHighlightedMailboxId,
long nextSelectedMailboxId) {
mNextParentMailboxId = nextParentMailboxId;
mNextHighlightedMailboxId = nextHighlightedMailboxId;
mNextSelectedMailboxId = nextSelectedMailboxId;
}
}
}