diff --git a/src/com/android/email/activity/MailboxFragmentAdapter.java b/src/com/android/email/activity/MailboxFragmentAdapter.java index 9e2359d77..8538e7455 100644 --- a/src/com/android/email/activity/MailboxFragmentAdapter.java +++ b/src/com/android/email/activity/MailboxFragmentAdapter.java @@ -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 parentKey + * Returns a cursor loader for the mailboxes of the given account. If parentKey * 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 createLoader(Context context, long accountId, long parentKey) { + public static Loader 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 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); } } diff --git a/src/com/android/email/activity/MailboxListFragment.java b/src/com/android/email/activity/MailboxListFragment.java index 03b456f7d..5b0d5aae4 100644 --- a/src/com/android/email/activity/MailboxListFragment.java +++ b/src/com/android/email/activity/MailboxListFragment.java @@ -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. + * + *
+ * 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}.
+ * 
+ * + * + * This fragment shows the content in one of the three following views, depending on the + * parameters above. + * + *
+ * 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.
+ * 
+ * * * 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 must 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()}. *

NOTE: Although we cannot force these to be immutable using Java language * constructs, this must be considered immutable. */ - private Long mImmutableParentMailboxId; + private long mImmutableInitialCurrentMailboxId; + + /** + * {@code enableHighlight} passed to {@link #newInstance}. + * Do not use directly; instead, use {@link #getEnableHighlight()}. + *

NOTE: Although we cannot force these to be immutable using Java language + * constructs, this must 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 { + 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 { - /** 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); } }); } diff --git a/src/com/android/email/activity/UIControllerOnePane.java b/src/com/android/email/activity/UIControllerOnePane.java index 292dbb368..4b503dbec 100644 --- a/src/com/android/email/activity/UIControllerOnePane.java +++ b/src/com/android/email/activity/UIControllerOnePane.java @@ -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; diff --git a/src/com/android/email/activity/UIControllerTwoPane.java b/src/com/android/email/activity/UIControllerTwoPane.java index 6e55ad32a..56a803851 100644 --- a/src/com/android/email/activity/UIControllerTwoPane.java +++ b/src/com/android/email/activity/UIControllerTwoPane.java @@ -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(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 * forceReload is true. * * @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()); } } } diff --git a/src/com/android/email/activity/Welcome.java b/src/com/android/email/activity/Welcome.java index c4d4d4c91..af085bc30 100644 --- a/src/com/android/email/activity/Welcome.java +++ b/src/com/android/email/activity/Welcome.java @@ -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 */ /** diff --git a/src/com/android/email/data/CursorWithExtras.java b/src/com/android/email/data/CursorWithExtras.java deleted file mode 100644 index 8cb624cb0..000000000 --- a/src/com/android/email/data/CursorWithExtras.java +++ /dev/null @@ -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. - * - *

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); - } -} diff --git a/tests/src/com/android/email/activity/FindParentMailboxTaskTest.java b/tests/src/com/android/email/activity/FindParentMailboxTaskTest.java new file mode 100644 index 000000000..4e28b917d --- /dev/null +++ b/tests/src/com/android/email/activity/FindParentMailboxTaskTest.java @@ -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: + *

+     * |-Inbox
+     * |-Parent
+     *   |-Child1
+     *   |-Child2
+     *     |-GrandChild1
+     *     |-GrandChild2
+     * 
+ */ + 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; + } + } +}