Home | History | Annotate | Download | only in activity
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.email.activity;
     18 
     19 import android.app.Activity;
     20 import android.app.ListFragment;
     21 import android.app.LoaderManager;
     22 import android.app.LoaderManager.LoaderCallbacks;
     23 import android.content.ClipData;
     24 import android.content.ClipDescription;
     25 import android.content.Context;
     26 import android.content.Loader;
     27 import android.database.Cursor;
     28 import android.graphics.Rect;
     29 import android.net.Uri;
     30 import android.os.Bundle;
     31 import android.os.Parcelable;
     32 import android.util.Log;
     33 import android.view.DragEvent;
     34 import android.view.LayoutInflater;
     35 import android.view.View;
     36 import android.view.View.OnDragListener;
     37 import android.view.ViewGroup;
     38 import android.widget.AdapterView;
     39 import android.widget.AdapterView.OnItemClickListener;
     40 import android.widget.ListView;
     41 
     42 import com.android.email.Controller;
     43 import com.android.email.Email;
     44 import com.android.email.R;
     45 import com.android.email.RefreshManager;
     46 import com.android.email.provider.EmailProvider;
     47 import com.android.emailcommon.Logging;
     48 import com.android.emailcommon.provider.Account;
     49 import com.android.emailcommon.provider.Mailbox;
     50 import com.android.emailcommon.utility.EmailAsyncTask;
     51 import com.android.emailcommon.utility.Utility;
     52 import com.google.common.annotations.VisibleForTesting;
     53 
     54 import java.util.Timer;
     55 
     56 /**
     57  * This fragment presents a list of mailboxes for a given account or the combined mailboxes.
     58  *
     59  * This fragment has several parameters that determine the current view.
     60  *
     61  * <pre>
     62  * Parameters:
     63  * - Account ID.
     64  *   - Set via {@link #newInstance}.
     65  *   - Can be obtained with {@link #getAccountId()}.
     66  *   - Will not change throughout fragment lifecycle.
     67  *   - Either an actual account ID, or {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
     68  *
     69  * - "Highlight enabled?" flag
     70  *   - Set via {@link #newInstance}.
     71  *   - Can be obtained with {@link #getEnableHighlight()}.
     72  *   - Will not change throughout fragment lifecycle.
     73  *   - If {@code true}, we highlight the "selected" mailbox (used only on 2-pane).
     74  *   - Note even if it's {@code true}, there may be no highlighted mailbox.
     75  *     (This usually happens on 2-pane before the UI controller finds the Inbox to highlight.)
     76  *
     77  * - "Parent" mailbox ID
     78  *   - Stored in {@link #mParentMailboxId}
     79  *   - Changes as the user navigates through nested mailboxes.
     80  *   - Initialized using the {@code mailboxId} parameter for {@link #newInstance}
     81  *     in {@link #setInitialParentAndHighlight()}.
     82  *
     83  * - "Highlighted" mailbox
     84  *   - Only used when highlighting is enabled.  (Otherwise always {@link Mailbox#NO_MAILBOX}.)
     85  *     i.e. used only on two-pane.
     86  *   - Stored in {@link #mHighlightedMailboxId}
     87  *   - Initialized using the {@code mailboxId} parameter for {@link #newInstance}
     88  *     in {@link #setInitialParentAndHighlight()}.
     89  *
     90  *   - Can be changed any time, using {@link #setHighlightedMailbox(long)}.
     91  *
     92  *   - If set, it's considered "selected", and we highlight the list item.
     93  *
     94  *   - (It should always be the ID of the list item selected in the list view, but we store it in
     95  *     a member for efficiency.)
     96  *
     97  *   - Sometimes, we need to set the highlighted mailbox while we're still loading data.
     98  *     In this case, we can't update {@link #mHighlightedMailboxId} right away, but need to do so
     99  *     in when the next data set arrives, in
    100  *     {@link MailboxListFragment.MailboxListLoaderCallbacks#onLoadFinished}.  For this, we use
    101  *     we store the mailbox ID in {@link #mNextHighlightedMailboxId} and update
    102  *     {@link #mHighlightedMailboxId} in onLoadFinished.
    103  *
    104  *
    105  * The "selected" is defined using the "parent" and "highlighted" mailboxes.
    106  * - "Selected" mailbox  (also sometimes called "current".)
    107  *   - This is what the user thinks it's now selected.
    108  *
    109  *   - Can be obtained with {@link #getSelectedMailboxId()}
    110  *   - If the "highlighted" mailbox exists, it's the "selected."  Otherwise, the "parent"
    111  *     is considered "selected."
    112  *   - This is what is passed to {@link Callback#onMailboxSelected}.
    113  * </pre>
    114  *
    115  *
    116  * This fragment shows the content in one of the three following views, depending on the
    117  * parameters above.
    118  *
    119  * <pre>
    120  * 1. Combined view
    121  *   - Used if the account ID == {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
    122  *   - Parent mailbox is always {@link Mailbox#NO_MAILBOX}.
    123  *   - List contains:
    124  *     - combined mailboxes
    125  *     - all accounts
    126  *
    127  * 2. Root view for an account
    128  *   - Used if the account ID != {@link Account#ACCOUNT_ID_COMBINED_VIEW} and
    129  *     Parent mailbox == {@link Mailbox#NO_MAILBOX}
    130  *   - List contains
    131  *     - all the top level mailboxes for the selected account.
    132  *
    133  * 3. Root view for a mailbox.  (nested view)
    134  *   - Used if the account ID != {@link Account#ACCOUNT_ID_COMBINED_VIEW} and
    135  *     Parent mailbox != {@link Mailbox#NO_MAILBOX}
    136  *   - List contains:
    137  *     - parent mailbox (determined by "parent" mailbox ID)
    138  *     - all child mailboxes of the parent mailbox.
    139  * </pre>
    140  *
    141  *
    142  * Note that when a fragment is put in the back stack, it'll lose the content view but the fragment
    143  * itself is not destroyed.  If you call {@link #getListView()} in this state it'll throw
    144  * an {@link IllegalStateException}.  So,
    145  * - If code is supposed to be executed only when the fragment has the content view, use
    146  *   {@link #getListView()} directly to make sure it doesn't accidentally get executed when there's
    147  *   no views.
    148  * - Otherwise, make sure to check if the fragment has views with {@link #isViewCreated()}
    149  *   before touching any views.
    150  */
    151 public class MailboxListFragment extends ListFragment implements OnItemClickListener,
    152         OnDragListener {
    153     private static final String TAG = "MailboxListFragment";
    154 
    155     private static final String BUNDLE_KEY_PARENT_MAILBOX_ID
    156             = "MailboxListFragment.state.parent_mailbox_id";
    157     private static final String BUNDLE_KEY_HIGHLIGHTED_MAILBOX_ID
    158             = "MailboxListFragment.state.selected_mailbox_id";
    159     private static final String BUNDLE_LIST_STATE = "MailboxListFragment.state.listState";
    160     private static final boolean DEBUG_DRAG_DROP = false; // MUST NOT SUBMIT SET TO TRUE
    161 
    162     /** No drop target is available where the user is currently hovering over */
    163     private static final int NO_DROP_TARGET = -1;
    164     // Total height of the top and bottom scroll zones, in pixels
    165     private static final int SCROLL_ZONE_SIZE = 64;
    166     // The amount of time to scroll by one pixel, in ms
    167     private static final int SCROLL_SPEED = 4;
    168 
    169     /** Arbitrary number for use with the loader manager */
    170     private static final int MAILBOX_LOADER_ID = 1;
    171 
    172     /** Argument name(s) */
    173     private static final String ARG_ACCOUNT_ID = "accountId";
    174     private static final String ARG_ENABLE_HIGHLIGHT = "enablehighlight";
    175     private static final String ARG_INITIAL_CURRENT_MAILBOX_ID = "initialParentMailboxId";
    176 
    177     private final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker();
    178 
    179     /** Rectangle used for hit testing children */
    180     private static final Rect sTouchFrame = new Rect();
    181 
    182     private RefreshManager mRefreshManager;
    183 
    184     // UI Support
    185     private Activity mActivity;
    186     private MailboxFragmentAdapter mListAdapter;
    187     private Callback mCallback = EmptyCallback.INSTANCE;
    188 
    189     // See the class javadoc
    190     private long mParentMailboxId;
    191     private long mHighlightedMailboxId;
    192 
    193     /**
    194      * Becomes {@code true} once we determine which mailbox to use as the parent.
    195      */
    196     private boolean mParentDetermined;
    197 
    198     /**
    199      * ID of the mailbox that should be highlighted when the next cursor is loaded.
    200      */
    201     private long mNextHighlightedMailboxId = Mailbox.NO_MAILBOX;
    202 
    203     // True if a drag is currently in progress
    204     private boolean mDragInProgress;
    205     /** Mailbox ID of the item being dragged. Used to determine valid drop targets. */
    206     private long mDragItemMailboxId = -1;
    207     /** A unique identifier for the drop target. May be {@link #NO_DROP_TARGET}. */
    208     private int mDropTargetId = NO_DROP_TARGET;
    209     // The mailbox list item view that the user's finger is hovering over
    210     private MailboxListItem mDropTargetView;
    211     // Lazily instantiated height of a mailbox list item (-1 is a sentinel for 'not initialized')
    212     private int mDragItemHeight = -1;
    213     /** {@code true} if we are currently scrolling under the drag item */
    214     private boolean mTargetScrolling;
    215 
    216     private Parcelable mSavedListState;
    217 
    218     private final MailboxFragmentAdapter.Callback mMailboxesAdapterCallback =
    219             new MailboxFragmentAdapter.Callback() {
    220         @Override
    221         public void onBind(MailboxListItem listItem) {
    222             listItem.setDropTargetBackground(mDragInProgress, mDragItemMailboxId);
    223         }
    224     };
    225 
    226     /**
    227      * Callback interface that owning activities must implement
    228      */
    229     public interface Callback {
    230         /**
    231          * Called when any mailbox (even a combined mailbox) is selected.
    232          *
    233          * @param accountId
    234          *          The ID of the owner account of the selected mailbox.
    235          *          Or {@link Account#ACCOUNT_ID_COMBINED_VIEW} if it's a combined mailbox.
    236          * @param mailboxId
    237          *          The ID of the selected mailbox. This may be real mailbox ID [e.g. a number > 0],
    238          *          or a combined mailbox ID [e.g. {@link Mailbox#QUERY_ALL_INBOXES}].
    239          * @param nestedNavigation {@code true} if the event is caused by nested mailbox navigation,
    240          *          that is, going up or drilling-in to a child mailbox.
    241          */
    242         public void onMailboxSelected(long accountId, long mailboxId, boolean nestedNavigation);
    243 
    244         /** Called when an account is selected on the combined view. */
    245         public void onAccountSelected(long accountId);
    246 
    247         /**
    248          * Called when the parent mailbox is changing.
    249          */
    250         public void onParentMailboxChanged();
    251     }
    252 
    253     private static class EmptyCallback implements Callback {
    254         public static final Callback INSTANCE = new EmptyCallback();
    255         @Override public void onMailboxSelected(long accountId, long mailboxId,
    256                 boolean nestedNavigation) { }
    257         @Override public void onAccountSelected(long accountId) { }
    258         @Override
    259         public void onParentMailboxChanged() { }
    260     }
    261 
    262     /**
    263      * Returns the index of the view located at the specified coordinates in the given list.
    264      * If the coordinates are outside of the list, {@code NO_DROP_TARGET} is returned.
    265      */
    266     private static int pointToIndex(ListView list, int x, int y) {
    267         final int count = list.getChildCount();
    268         for (int i = count - 1; i >= 0; i--) {
    269             final View child = list.getChildAt(i);
    270             if (child.getVisibility() == View.VISIBLE) {
    271                 child.getHitRect(sTouchFrame);
    272                 if (sTouchFrame.contains(x, y)) {
    273                     return i;
    274                 }
    275             }
    276         }
    277         return NO_DROP_TARGET;
    278     }
    279 
    280     /**
    281      * Create a new instance with initialization parameters.
    282      *
    283      * This fragment should be created only with this method.  (Arguments should always be set.)
    284      *
    285      * @param accountId The ID of the account we want to view
    286      * @param initialCurrentMailboxId ID of the mailbox of interest.
    287      *        Pass {@link Mailbox#NO_MAILBOX} to show top-level mailboxes.
    288      * @param enableHighlight {@code true} if highlighting is enabled on the current screen
    289      *        configuration.  (We don't highlight mailboxes on one-pane.)
    290      */
    291     public static MailboxListFragment newInstance(long accountId, long initialCurrentMailboxId,
    292             boolean enableHighlight) {
    293         final MailboxListFragment instance = new MailboxListFragment();
    294         final Bundle args = new Bundle();
    295         args.putLong(ARG_ACCOUNT_ID, accountId);
    296         args.putLong(ARG_INITIAL_CURRENT_MAILBOX_ID, initialCurrentMailboxId);
    297         args.putBoolean(ARG_ENABLE_HIGHLIGHT, enableHighlight);
    298         instance.setArguments(args);
    299         return instance;
    300     }
    301 
    302     /**
    303      * The account ID the mailbox is associated with. Do not use directly; instead, use
    304      * {@link #getAccountId()}.
    305      * <p><em>NOTE:</em> Although we cannot force these to be immutable using Java language
    306      * constructs, this <em>must</em> be considered immutable.
    307      */
    308     private Long mImmutableAccountId;
    309 
    310     /**
    311      * {@code initialCurrentMailboxId} passed to {@link #newInstance}.
    312      * Do not use directly; instead, use {@link #getInitialCurrentMailboxId()}.
    313      * <p><em>NOTE:</em> Although we cannot force these to be immutable using Java language
    314      * constructs, this <em>must</em> be considered immutable.
    315      */
    316     private long mImmutableInitialCurrentMailboxId;
    317 
    318     /**
    319      * {@code enableHighlight} passed to {@link #newInstance}.
    320      * Do not use directly; instead, use {@link #getEnableHighlight()}.
    321      * <p><em>NOTE:</em> Although we cannot force these to be immutable using Java language
    322      * constructs, this <em>must</em> be considered immutable.
    323      */
    324     private boolean mImmutableEnableHighlight;
    325 
    326     private void initializeArgCache() {
    327         if (mImmutableAccountId != null) return;
    328         mImmutableAccountId = getArguments().getLong(ARG_ACCOUNT_ID);
    329         mImmutableInitialCurrentMailboxId = getArguments().getLong(ARG_INITIAL_CURRENT_MAILBOX_ID);
    330         mImmutableEnableHighlight = getArguments().getBoolean(ARG_ENABLE_HIGHLIGHT);
    331     }
    332 
    333     /**
    334      * @return {@code accountId} passed to {@link #newInstance}.  Safe to call even before onCreate.
    335      */
    336     public long getAccountId() {
    337         initializeArgCache();
    338         return mImmutableAccountId;
    339     }
    340 
    341     /**
    342      * @return {@code initialCurrentMailboxId} passed to {@link #newInstance}.
    343      * Safe to call even before onCreate.
    344      */
    345     public long getInitialCurrentMailboxId() {
    346         initializeArgCache();
    347         return mImmutableInitialCurrentMailboxId;
    348     }
    349 
    350     /**
    351      * @return {@code enableHighlight} passed to {@link #newInstance}.
    352      * Safe to call even before onCreate.
    353      */
    354     public boolean getEnableHighlight() {
    355         initializeArgCache();
    356         return mImmutableEnableHighlight;
    357     }
    358 
    359     @Override
    360     public void onAttach(Activity activity) {
    361         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    362             Log.d(Logging.LOG_TAG, this + " onAttach");
    363         }
    364         super.onAttach(activity);
    365     }
    366 
    367     /**
    368      * Called to do initial creation of a fragment.  This is called after
    369      * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}.
    370      */
    371     @Override
    372     public void onCreate(Bundle savedInstanceState) {
    373         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    374             Log.d(Logging.LOG_TAG, this + " onCreate");
    375         }
    376         super.onCreate(savedInstanceState);
    377 
    378         mActivity = getActivity();
    379         mRefreshManager = RefreshManager.getInstance(mActivity);
    380         mListAdapter = new MailboxFragmentAdapter(mActivity, mMailboxesAdapterCallback);
    381         setListAdapter(mListAdapter); // It's safe to do even before the list view is created.
    382 
    383         if (savedInstanceState == null) {
    384             setInitialParentAndHighlight();
    385         } else {
    386             restoreInstanceState(savedInstanceState);
    387         }
    388     }
    389 
    390     /**
    391      * Set {@link #mParentMailboxId} and {@link #mHighlightedMailboxId} from the fragment arguments.
    392      */
    393     private void setInitialParentAndHighlight() {
    394         final long initialMailboxId = getInitialCurrentMailboxId();
    395         if (getAccountId() == Account.ACCOUNT_ID_COMBINED_VIEW) {
    396             // For the combined view, always show the top-level, but highlight the "current".
    397             mParentMailboxId = Mailbox.NO_MAILBOX;
    398         } else {
    399             // Inbox needs special care.
    400             // Note we can't get the mailbox type on the UI thread but this method *can* be used...
    401             final long inboxId = Mailbox.findMailboxOfType(getActivity(), getAccountId(),
    402                     Mailbox.TYPE_INBOX);
    403             if (initialMailboxId == inboxId) {
    404                 // If Inbox is set as the initial current, we show the top level mailboxes
    405                 // with inbox highlighted.
    406                 mParentMailboxId = Mailbox.NO_MAILBOX;
    407             } else {
    408                 // Otherwise, try using the "current" as the "parent" (and also highlight it).
    409                 // If it has no children, we go up in onLoadFinished().
    410                 mParentMailboxId = initialMailboxId;
    411             }
    412         }
    413         // Highlight the mailbox of interest
    414         if (getEnableHighlight()) {
    415             mHighlightedMailboxId = initialMailboxId;
    416         }
    417     }
    418 
    419     @Override
    420     public View onCreateView(
    421             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    422         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    423             Log.d(Logging.LOG_TAG, this + " onCreateView");
    424         }
    425         return inflater.inflate(R.layout.mailbox_list_fragment, container, false);
    426     }
    427 
    428     /**
    429      * @return true if the content view is created and not destroyed yet. (i.e. between
    430      * {@link #onCreateView} and {@link #onDestroyView}.
    431      */
    432     private boolean isViewCreated() {
    433         return getView() != null;
    434     }
    435 
    436     @Override
    437     public void onActivityCreated(Bundle savedInstanceState) {
    438         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    439             Log.d(Logging.LOG_TAG, this + " onActivityCreated");
    440         }
    441         super.onActivityCreated(savedInstanceState);
    442 
    443         // Note we can't do this in onCreateView.
    444         // getListView() is only usable after onCreateView().
    445         final ListView lv = getListView();
    446         lv.setOnItemClickListener(this);
    447         lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
    448         lv.setOnDragListener(this);
    449 
    450         startLoading(mParentMailboxId, mHighlightedMailboxId);
    451 
    452         UiUtilities.installFragment(this);
    453     }
    454 
    455     public void setCallback(Callback callback) {
    456         mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback;
    457     }
    458 
    459     /**
    460      * Called when the Fragment is visible to the user.
    461      */
    462     @Override
    463     public void onStart() {
    464         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    465             Log.d(Logging.LOG_TAG, this + " onStart");
    466         }
    467         super.onStart();
    468     }
    469 
    470     /**
    471      * Called when the fragment is visible to the user and actively running.
    472      */
    473     @Override
    474     public void onResume() {
    475         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    476             Log.d(Logging.LOG_TAG, this + " onResume");
    477         }
    478         super.onResume();
    479 
    480         // Fetch the latest mailbox list from the server here if stale so that the user always
    481         // sees the (reasonably) up-to-date mailbox list, without pressing "refresh".
    482         final long accountId = getAccountId();
    483         if (mRefreshManager.isMailboxListStale(accountId)) {
    484             mRefreshManager.refreshMailboxList(accountId);
    485         }
    486     }
    487 
    488     @Override
    489     public void onPause() {
    490         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    491             Log.d(Logging.LOG_TAG, this + " onPause");
    492         }
    493         mSavedListState = getListView().onSaveInstanceState();
    494         super.onPause();
    495     }
    496 
    497     /**
    498      * Called when the Fragment is no longer started.
    499      */
    500     @Override
    501     public void onStop() {
    502         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    503             Log.d(Logging.LOG_TAG, this + " onStop");
    504         }
    505         super.onStop();
    506     }
    507 
    508     @Override
    509     public void onDestroyView() {
    510         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    511             Log.d(Logging.LOG_TAG, this + " onDestroyView");
    512         }
    513         UiUtilities.uninstallFragment(this);
    514         super.onDestroyView();
    515     }
    516 
    517     /**
    518      * Called when the fragment is no longer in use.
    519      */
    520     @Override
    521     public void onDestroy() {
    522         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    523             Log.d(Logging.LOG_TAG, this + " onDestroy");
    524         }
    525         mTaskTracker.cancellAllInterrupt();
    526         super.onDestroy();
    527     }
    528 
    529     @Override
    530     public void onDetach() {
    531         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    532             Log.d(Logging.LOG_TAG, this + " onDetach");
    533         }
    534         super.onDetach();
    535     }
    536 
    537     @Override
    538     public void onSaveInstanceState(Bundle outState) {
    539         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    540             Log.d(Logging.LOG_TAG, this + " onSaveInstanceState");
    541         }
    542         super.onSaveInstanceState(outState);
    543         outState.putLong(BUNDLE_KEY_PARENT_MAILBOX_ID, mParentMailboxId);
    544         outState.putLong(BUNDLE_KEY_HIGHLIGHTED_MAILBOX_ID, mHighlightedMailboxId);
    545         if (isViewCreated()) {
    546             outState.putParcelable(BUNDLE_LIST_STATE, getListView().onSaveInstanceState());
    547         }
    548     }
    549 
    550     private void restoreInstanceState(Bundle savedInstanceState) {
    551         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    552             Log.d(Logging.LOG_TAG, this + " restoreInstanceState");
    553         }
    554         mParentMailboxId = savedInstanceState.getLong(BUNDLE_KEY_PARENT_MAILBOX_ID);
    555         mNextHighlightedMailboxId = savedInstanceState.getLong(BUNDLE_KEY_HIGHLIGHTED_MAILBOX_ID);
    556         mSavedListState = savedInstanceState.getParcelable(BUNDLE_LIST_STATE);
    557     }
    558 
    559     /**
    560      * @return "Selected" mailbox ID.
    561      */
    562     public long getSelectedMailboxId() {
    563         return (mHighlightedMailboxId != Mailbox.NO_MAILBOX) ? mHighlightedMailboxId
    564                 : mParentMailboxId;
    565     }
    566 
    567     /**
    568      * @return {@code true} if top-level mailboxes are shown.  {@code false} otherwise.
    569      */
    570     private boolean isRoot() {
    571         return mParentMailboxId == Mailbox.NO_MAILBOX;
    572     }
    573 
    574     /**
    575      * Navigate one level up in the mailbox hierarchy. Does nothing if at the root account view.
    576      */
    577     public boolean navigateUp() {
    578         if (isRoot()) {
    579             return false;
    580         }
    581         FindParentMailboxTask.ResultCallback callback = new FindParentMailboxTask.ResultCallback() {
    582             @Override public void onResult(long nextParentMailboxId,
    583                     long nextHighlightedMailboxId, long nextSelectedMailboxId) {
    584 
    585                 startLoading(nextParentMailboxId, nextHighlightedMailboxId);
    586             }
    587         };
    588         new FindParentMailboxTask(
    589                 getActivity().getApplicationContext(), mTaskTracker, getAccountId(),
    590                 getEnableHighlight(), mParentMailboxId, mHighlightedMailboxId, callback
    591                 ).cancelPreviousAndExecuteParallel((Void[]) null);
    592         return true;
    593     }
    594 
    595     /**
    596      * @return {@code true} if the fragment is showing nested mailboxes and we can go one level up.
    597      *         {@code false} otherwise, meaning we're showing the top level mailboxes *OR*
    598      *         we're still loading initial data and we can't determine if we're going to show
    599      *         top-level or not.
    600      */
    601     public boolean canNavigateUp() {
    602         if (!mParentDetermined) {
    603             return false; // We can't determine yet...
    604         }
    605         return !isRoot();
    606     }
    607 
    608     /**
    609      * A task to determine what parent mailbox ID/highlighted mailbox ID to use for the "UP"
    610      * navigation, given the current parent mailbox ID, the highlighted mailbox ID, and {@link
    611      * #mEnableHighlight}.
    612      */
    613     @VisibleForTesting
    614     static class FindParentMailboxTask extends EmailAsyncTask<Void, Void, Long[]> {
    615         public interface ResultCallback {
    616             /**
    617              * Callback to get the result.
    618              *
    619              * @param nextParentMailboxId ID of the mailbox to use
    620              * @param nextHighlightedMailboxId ID of the mailbox to highlight
    621              * @param nextSelectedMailboxId ID of the mailbox to notify with
    622              *        {@link Callback#onMailboxSelected}.
    623              */
    624             public void onResult(long nextParentMailboxId, long nextHighlightedMailboxId,
    625                     long nextSelectedMailboxId);
    626         }
    627 
    628         private final Context mContext;
    629         private final long mAccountId;
    630         private final boolean mEnableHighlight;
    631         private final long mParentMailboxId;
    632         private final long mHighlightedMailboxId;
    633         private final ResultCallback mCallback;
    634 
    635         public FindParentMailboxTask(Context context, EmailAsyncTask.Tracker taskTracker,
    636                 long accountId, boolean enableHighlight, long parentMailboxId,
    637                 long highlightedMailboxId, ResultCallback callback) {
    638             super(taskTracker);
    639             mContext = context;
    640             mAccountId = accountId;
    641             mEnableHighlight = enableHighlight;
    642             mParentMailboxId = parentMailboxId;
    643             mHighlightedMailboxId = highlightedMailboxId;
    644             mCallback = callback;
    645         }
    646 
    647         @Override
    648         protected Long[] doInBackground(Void... params) {
    649             Mailbox parentMailbox = Mailbox.restoreMailboxWithId(mContext, mParentMailboxId);
    650             final long nextParentId = (parentMailbox == null) ? Mailbox.NO_MAILBOX
    651                     : parentMailbox.mParentKey;
    652             final long nextHighlightedId;
    653             final long nextSelectedId;
    654             if (mEnableHighlight) {
    655                 // If the "parent" is highlighted before the transition, it should still be
    656                 // highlighted after the upper level view.
    657                 if (mParentMailboxId == mHighlightedMailboxId) {
    658                     nextHighlightedId = mParentMailboxId;
    659                 } else {
    660                     // Otherwise, the next parent will be highlighted, unless we're going up to
    661                     // the root, in which case Inbox should be highlighted.
    662                     if (nextParentId == Mailbox.NO_MAILBOX) {
    663                         nextHighlightedId = Mailbox.findMailboxOfType(mContext, mAccountId,
    664                                 Mailbox.TYPE_INBOX);
    665                     } else {
    666                         nextHighlightedId = nextParentId;
    667                     }
    668                 }
    669 
    670                 // Highlighted one will be "selected".
    671                 nextSelectedId = nextHighlightedId;
    672 
    673             } else { // !mEnableHighlight
    674                 nextHighlightedId = Mailbox.NO_MAILBOX;
    675 
    676                 // Parent will be selected.
    677                 nextSelectedId = nextParentId;
    678             }
    679             return new Long[]{nextParentId, nextHighlightedId, nextSelectedId};
    680         }
    681 
    682         @Override
    683         protected void onSuccess(Long[] result) {
    684             mCallback.onResult(result[0], result[1], result[2]);
    685         }
    686     }
    687 
    688     /**
    689      * Starts the loader.
    690      *
    691      * @param parentMailboxId Mailbox ID to be used as the "parent" mailbox
    692      * @param highlightedMailboxId Mailbox ID that should be highlighted when the data is loaded.
    693      */
    694     private void startLoading(long parentMailboxId, long highlightedMailboxId) {
    695         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    696             Log.d(Logging.LOG_TAG, this + " startLoading  parent=" + parentMailboxId
    697                     + " highlighted=" + highlightedMailboxId);
    698         }
    699         final LoaderManager lm = getLoaderManager();
    700         boolean parentMailboxChanging = false;
    701 
    702         // Parent mailbox changing -- destroy the current loader to force reload.
    703         if (mParentMailboxId != parentMailboxId) {
    704             lm.destroyLoader(MAILBOX_LOADER_ID);
    705             setListShown(false);
    706             parentMailboxChanging = true;
    707         }
    708         mParentMailboxId = parentMailboxId;
    709         if (getEnableHighlight()) {
    710             mNextHighlightedMailboxId = highlightedMailboxId;
    711         }
    712 
    713         lm.initLoader(MAILBOX_LOADER_ID, null, new MailboxListLoaderCallbacks());
    714 
    715         if (parentMailboxChanging) {
    716             mCallback.onParentMailboxChanged();
    717         }
    718     }
    719 
    720     /**
    721      * Highlight the given mailbox.
    722      *
    723      * If data is already loaded, it just sets {@link #mHighlightedMailboxId} and highlight the
    724      * corresponding list item.  (And if the corresponding list item is not found,
    725      * {@link #mHighlightedMailboxId} is set to {@link Mailbox#NO_MAILBOX})
    726      *
    727      * If we're still loading data, it sets {@link #mNextHighlightedMailboxId} instead, and then
    728      * it'll be set to {@link #mHighlightedMailboxId} in
    729      * {@link MailboxListLoaderCallbacks#onLoadFinished}.
    730      *
    731      * @param mailboxId The ID of the mailbox to highlight.
    732      */
    733     public void setHighlightedMailbox(long mailboxId) {
    734         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    735             Log.d(Logging.LOG_TAG, this + " setHighlightedMailbox  mailbox=" + mailboxId);
    736         }
    737         if (!getEnableHighlight()) {
    738             return;
    739         }
    740         if (mHighlightedMailboxId == mailboxId) {
    741             return; // already highlighted.
    742         }
    743         if (mListAdapter.getCursor() == null) {
    744             // List not loaded yet.  Just remember the ID here and let onLoadFinished() update
    745             // mHighlightedMailboxId.
    746             mNextHighlightedMailboxId = mailboxId;
    747             return;
    748         }
    749         mHighlightedMailboxId = mailboxId;
    750         updateHighlightedMailbox(true);
    751     }
    752 
    753     // TODO This class probably should be made static. There are many calls into the enclosing
    754     // class and we need to be cautious about what we call while in these callbacks
    755     private class MailboxListLoaderCallbacks implements LoaderCallbacks<Cursor> {
    756         private boolean mIsFirstLoad;
    757 
    758         @Override
    759         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    760             if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    761                 Log.d(Logging.LOG_TAG, MailboxListFragment.this + " onCreateLoader");
    762             }
    763             mIsFirstLoad = true;
    764             if (getAccountId() == Account.ACCOUNT_ID_COMBINED_VIEW) {
    765                 return MailboxFragmentAdapter.createCombinedViewLoader(getActivity());
    766             } else {
    767                 return MailboxFragmentAdapter.createMailboxesLoader(getActivity(), getAccountId(),
    768                         mParentMailboxId);
    769             }
    770         }
    771 
    772         @Override
    773         public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    774             if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    775                 Log.d(Logging.LOG_TAG, MailboxListFragment.this + " onLoadFinished  count="
    776                         + cursor.getCount());
    777             }
    778             // Note in onLoadFinished we can assume the view is created.
    779             // The loader manager doesn't deliver results when a fragment is stopped.
    780 
    781             // If we're showing a nested mailboxes, and the current parent mailbox has no children,
    782             // go up.
    783             if (getAccountId() != Account.ACCOUNT_ID_COMBINED_VIEW) {
    784                 MailboxFragmentAdapter.CursorWithExtras c =
    785                         (MailboxFragmentAdapter.CursorWithExtras) cursor;
    786                 if ((c.mChildCount == 0) && !isRoot()) {
    787                     // Always swap out the cursor so we don't hold a reference to a stale one.
    788                     mListAdapter.swapCursor(cursor);
    789                     navigateUp();
    790                     return;
    791                 }
    792             }
    793 
    794             // Save list view state (primarily scroll position)
    795             final ListView lv = getListView();
    796             final Parcelable listState;
    797             if (mSavedListState != null) {
    798                 listState = mSavedListState;
    799                 mSavedListState = null;
    800             } else {
    801                 listState = lv.onSaveInstanceState();
    802             }
    803 
    804             if (cursor.getCount() == 0) {
    805                 // There's no row -- call setListShown(false) to make ListFragment show progress
    806                 // icon.
    807                 mListAdapter.swapCursor(null);
    808                 setListShown(false);
    809 
    810             } else {
    811                 mParentDetermined = true; // Okay now we're sure which mailbox is the parent.
    812 
    813                 mListAdapter.swapCursor(cursor);
    814                 setListShown(true);
    815 
    816                 // Restore the list state, so scroll position is restored - this has to happen
    817                 // prior to setting the checked/highlighted mailbox below.
    818                 lv.onRestoreInstanceState(listState);
    819 
    820                 // Update the highlighted mailbox
    821                 if (mNextHighlightedMailboxId != Mailbox.NO_MAILBOX) {
    822                     setHighlightedMailbox(mNextHighlightedMailboxId);
    823                     mNextHighlightedMailboxId = Mailbox.NO_MAILBOX;
    824                 }
    825 
    826                 // We want to make visible the selection only for the first load.
    827                 // Re-load caused by content changed events shouldn't scroll the list.
    828                 if (!updateHighlightedMailbox(mIsFirstLoad)) {
    829                     // This may happen if the mailbox to be selected is not actually in the list
    830                     // that was loaded. Let the user just pick one manually if needed.
    831                     return;
    832                 }
    833             }
    834 
    835             // List has been reloaded; clear any drop target information
    836             mDropTargetId = NO_DROP_TARGET;
    837             mDropTargetView = null;
    838 
    839             mIsFirstLoad = false;
    840         }
    841 
    842         @Override
    843         public void onLoaderReset(Loader<Cursor> loader) {
    844             if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    845                 Log.d(Logging.LOG_TAG, MailboxListFragment.this + " onLoaderReset");
    846             }
    847             mListAdapter.swapCursor(null);
    848         }
    849     }
    850 
    851     /**
    852      * {@inheritDoc}
    853      * <p>
    854      * @param doNotUse <em>IMPORTANT</em>: Do not use this parameter. The ID in the list widget
    855      * must be a positive value. However, we rely on negative IDs for special mailboxes. Instead,
    856      * we use the ID returned by {@link MailboxFragmentAdapter#getId(int)}.
    857      */
    858     @Override
    859     public void onItemClick(AdapterView<?> parent, View view, int position, long doNotUse) {
    860         final long id = mListAdapter.getId(position);
    861         if (mListAdapter.isAccountRow(position)) {
    862             mCallback.onAccountSelected(id);
    863         } else if (mListAdapter.isMailboxRow(position)) {
    864             // Save account-id.  (Need to do this before startLoading() below, which will destroy
    865             // the current loader and make the mListAdapter lose the cursor.
    866             // Note, don't just use getAccountId().  A mailbox may tied to a different account ID
    867             // from getAccountId().  (Currently "Starred" does so.)
    868             long accountId = mListAdapter.getAccountId(position);
    869             boolean nestedNavigation = false;
    870             if (((MailboxListItem) view).isNavigable() && (id != mParentMailboxId)) {
    871                 // Drill-in.  Selected one will be the next parent, and it'll also be highlighted.
    872                 startLoading(id, id);
    873                 nestedNavigation = true;
    874             }
    875             if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
    876                 // Virtual mailboxes, such as "Starred", will have a "combined view" ID. However,
    877                 // we really want to relay the current active account, so that
    878                 // things like per-account starred mailboxes work as expected.
    879                 accountId = getAccountId();
    880             }
    881             mCallback.onMailboxSelected(accountId, id, nestedNavigation);
    882         }
    883     }
    884 
    885     /**
    886      * Really highlight the mailbox for {@link #mHighlightedMailboxId} on the list view.
    887      *
    888      * Note if a list item for {@link #mHighlightedMailboxId} is not found,
    889      * {@link #mHighlightedMailboxId} will be set to {@link Mailbox#NO_MAILBOX}.
    890      *
    891      * @return false when the highlighted mailbox seems to be gone; i.e. if
    892      *         {@link #mHighlightedMailboxId} is set but not found in the list.
    893      */
    894     private boolean updateHighlightedMailbox(boolean ensureSelectionVisible) {
    895         if (!getEnableHighlight() || !isViewCreated()) {
    896             return true; // Nothing to highlight
    897         }
    898         final ListView lv = getListView();
    899         boolean found = false;
    900         if (mHighlightedMailboxId == Mailbox.NO_MAILBOX) {
    901             // No mailbox selected
    902             lv.clearChoices();
    903             found = true;
    904         } else {
    905             // TODO Don't mix list view & list adapter indices. This is a recipe for disaster.
    906             final int count = lv.getCount();
    907             for (int i = 0; i < count; i++) {
    908                 if (mListAdapter.getId(i) != mHighlightedMailboxId) {
    909                     continue;
    910                 }
    911                 found = true;
    912                 lv.setItemChecked(i, true);
    913                 if (ensureSelectionVisible) {
    914                     Utility.listViewSmoothScrollToPosition(getActivity(), lv, i);
    915                 }
    916                 break;
    917             }
    918         }
    919         if (!found) {
    920             mHighlightedMailboxId = Mailbox.NO_MAILBOX;
    921         }
    922         return found;
    923     }
    924 
    925     // Drag & Drop handling
    926 
    927     /**
    928      * Update all of the list's child views with the proper target background (for now, orange if
    929      * a valid target, except red if the trash; standard background otherwise)
    930      */
    931     private void updateChildViews() {
    932         final ListView lv = getListView();
    933         int itemCount = lv.getChildCount();
    934         // Lazily initialize the height of our list items
    935         if (itemCount > 0 && mDragItemHeight < 0) {
    936             mDragItemHeight = lv.getChildAt(0).getHeight();
    937         }
    938         for (int i = 0; i < itemCount; i++) {
    939             final View child = lv.getChildAt(i);
    940             if (!(child instanceof MailboxListItem)) {
    941                 continue;
    942             }
    943             MailboxListItem item = (MailboxListItem) child;
    944             item.setDropTargetBackground(mDragInProgress, mDragItemMailboxId);
    945         }
    946     }
    947 
    948     /**
    949      * Called when the user has dragged outside of the mailbox list area.
    950      */
    951     private void onDragExited() {
    952         // Reset the background of the current target
    953         if (mDropTargetView != null) {
    954             mDropTargetView.setDropTargetBackground(mDragInProgress, mDragItemMailboxId);
    955             mDropTargetView = null;
    956         }
    957         mDropTargetId = NO_DROP_TARGET;
    958         stopScrolling();
    959     }
    960 
    961     /**
    962      * Called while dragging;  highlight possible drop targets, and auto scroll the list.
    963      */
    964     private void onDragLocation(DragEvent event) {
    965         final ListView lv = getListView();
    966         // TODO The list may be changing while in drag-n-drop; temporarily suspend drag-n-drop
    967         // if the list is being updated [i.e. navigated to another mailbox]
    968         if (mDragItemHeight <= 0) {
    969             // This shouldn't be possible, but avoid NPE
    970             Log.w(TAG, "drag item height is not set");
    971             return;
    972         }
    973         // Find out which item we're in and highlight as appropriate
    974         final int rawTouchX = (int) event.getX();
    975         final int rawTouchY = (int) event.getY();
    976         final int viewIndex = pointToIndex(lv, rawTouchX, rawTouchY);
    977         int targetId = viewIndex;
    978         if (targetId != mDropTargetId) {
    979             if (DEBUG_DRAG_DROP) {
    980                 Log.d(TAG, "=== Target changed; oldId: " + mDropTargetId + ", newId: " + targetId);
    981             }
    982             // Remove highlight the current target; if there was one
    983             if (mDropTargetView != null) {
    984                 mDropTargetView.setDropTargetBackground(true, mDragItemMailboxId);
    985                 mDropTargetView = null;
    986             }
    987             // Get the new target mailbox view
    988             final View childView = lv.getChildAt(viewIndex);
    989             final MailboxListItem newTarget;
    990             if (childView == null) {
    991                 // In any event, we're no longer dragging in the list view if newTarget is null
    992                 if (DEBUG_DRAG_DROP) {
    993                     Log.d(TAG, "=== Drag off the list");
    994                 }
    995                 newTarget = null;
    996                 final int childCount = lv.getChildCount();
    997                 if (viewIndex >= childCount) {
    998                     // Touching beyond the end of the list; may happen for small lists
    999                     onDragExited();
   1000                     return;
   1001                 } else {
   1002                     // We should never get here
   1003                     Log.w(TAG, "null view; idx: " + viewIndex + ", cnt: " + childCount);
   1004                 }
   1005             } else if (!(childView instanceof MailboxListItem)) {
   1006                 // We're over a header suchas "Recent folders".  We shouldn't finish DnD, but
   1007                 // drop should be disabled.
   1008                 newTarget = null;
   1009                 targetId = NO_DROP_TARGET;
   1010             } else {
   1011                 newTarget = (MailboxListItem) childView;
   1012                 if (newTarget.mMailboxType == Mailbox.TYPE_TRASH) {
   1013                     if (DEBUG_DRAG_DROP) {
   1014                         Log.d(TAG, "=== Trash mailbox; id: " + newTarget.mMailboxId);
   1015                     }
   1016                     newTarget.setDropTrashBackground();
   1017                 } else if (newTarget.isDropTarget(mDragItemMailboxId)) {
   1018                     if (DEBUG_DRAG_DROP) {
   1019                         Log.d(TAG, "=== Target mailbox; id: " + newTarget.mMailboxId);
   1020                     }
   1021                     newTarget.setDropActiveBackground();
   1022                 } else {
   1023                     if (DEBUG_DRAG_DROP) {
   1024                         Log.d(TAG, "=== Non-droppable mailbox; id: " + newTarget.mMailboxId);
   1025                     }
   1026                     newTarget.setDropTargetBackground(true, mDragItemMailboxId);
   1027                     targetId = NO_DROP_TARGET;
   1028                 }
   1029             }
   1030             // Save away our current position and view
   1031             mDropTargetId = targetId;
   1032             mDropTargetView = newTarget;
   1033         }
   1034 
   1035         // This is a quick-and-dirty implementation of drag-under-scroll; something like this
   1036         // should eventually find its way into the framework
   1037         int scrollDiff = rawTouchY - (lv.getHeight() - SCROLL_ZONE_SIZE);
   1038         boolean scrollDown = (scrollDiff > 0);
   1039         boolean scrollUp = (SCROLL_ZONE_SIZE > rawTouchY);
   1040         if (!mTargetScrolling && scrollDown) {
   1041             int itemsToScroll = lv.getCount() - lv.getLastVisiblePosition();
   1042             int pixelsToScroll = (itemsToScroll + 1) * mDragItemHeight;
   1043             lv.smoothScrollBy(pixelsToScroll, pixelsToScroll * SCROLL_SPEED);
   1044             if (DEBUG_DRAG_DROP) {
   1045                 Log.d(TAG, "=== Start scrolling list down");
   1046             }
   1047             mTargetScrolling = true;
   1048         } else if (!mTargetScrolling && scrollUp) {
   1049             int pixelsToScroll = (lv.getFirstVisiblePosition() + 1) * mDragItemHeight;
   1050             lv.smoothScrollBy(-pixelsToScroll, pixelsToScroll * SCROLL_SPEED);
   1051             if (DEBUG_DRAG_DROP) {
   1052                 Log.d(TAG, "=== Start scrolling list up");
   1053             }
   1054             mTargetScrolling = true;
   1055         } else if (!scrollUp && !scrollDown) {
   1056             stopScrolling();
   1057         }
   1058     }
   1059 
   1060     /**
   1061      * Indicate that scrolling has stopped
   1062      */
   1063     private void stopScrolling() {
   1064         final ListView lv = getListView();
   1065         if (mTargetScrolling) {
   1066             mTargetScrolling = false;
   1067             if (DEBUG_DRAG_DROP) {
   1068                 Log.d(TAG, "=== Stop scrolling list");
   1069             }
   1070             // Stop the scrolling
   1071             lv.smoothScrollBy(0, 0);
   1072         }
   1073     }
   1074 
   1075     private void onDragEnded() {
   1076         if (mDragInProgress) {
   1077             mDragInProgress = false;
   1078             // Reenable updates to the view and redraw (in case it changed)
   1079             MailboxFragmentAdapter.enableUpdates(true);
   1080             mListAdapter.notifyDataSetChanged();
   1081             // Stop highlighting targets
   1082             updateChildViews();
   1083             // Stop any scrolling that was going on
   1084             stopScrolling();
   1085         }
   1086     }
   1087 
   1088     private boolean onDragStarted(DragEvent event) {
   1089         // We handle dropping of items with our email mime type
   1090         // If the mime type has a mailbox id appended, that is the mailbox of the item
   1091         // being draged
   1092         ClipDescription description = event.getClipDescription();
   1093         int mimeTypeCount = description.getMimeTypeCount();
   1094         for (int i = 0; i < mimeTypeCount; i++) {
   1095             String mimeType = description.getMimeType(i);
   1096             if (mimeType.startsWith(EmailProvider.EMAIL_MESSAGE_MIME_TYPE)) {
   1097                 if (DEBUG_DRAG_DROP) {
   1098                     Log.d(TAG, "=== Drag started");
   1099                 }
   1100                 mDragItemMailboxId = -1;
   1101                 // See if we find a mailbox id here
   1102                 int dash = mimeType.lastIndexOf('-');
   1103                 if (dash > 0) {
   1104                     try {
   1105                         mDragItemMailboxId = Long.parseLong(mimeType.substring(dash + 1));
   1106                     } catch (NumberFormatException e) {
   1107                         // Ignore; we just won't know the mailbox
   1108                     }
   1109                 }
   1110                 mDragInProgress = true;
   1111                 // Stop the list from updating
   1112                 MailboxFragmentAdapter.enableUpdates(false);
   1113                 // Update the backgrounds of our child views to highlight drop targets
   1114                 updateChildViews();
   1115                 return true;
   1116             }
   1117         }
   1118         return false;
   1119     }
   1120 
   1121     /**
   1122      * Perform a "drop" action. If the user is not on top of a valid drop target, no action
   1123      * is performed.
   1124      * @return {@code true} if the drop action was performed. Otherwise {@code false}.
   1125      */
   1126     private boolean onDrop(DragEvent event) {
   1127         stopScrolling();
   1128         // If we're not on a target, we're done
   1129         if (mDropTargetId == NO_DROP_TARGET) {
   1130             return false;
   1131         }
   1132         final Controller controller = Controller.getInstance(mActivity);
   1133         ClipData clipData = event.getClipData();
   1134         int count = clipData.getItemCount();
   1135         if (DEBUG_DRAG_DROP) {
   1136             Log.d(TAG, "=== Dropping " + count + " items.");
   1137         }
   1138         // Extract the messageId's to move from the ClipData (set up in MessageListItem)
   1139         final long[] messageIds = new long[count];
   1140         for (int i = 0; i < count; i++) {
   1141             Uri uri = clipData.getItemAt(i).getUri();
   1142             String msgNum = uri.getPathSegments().get(1);
   1143             long id = Long.parseLong(msgNum);
   1144             messageIds[i] = id;
   1145         }
   1146         // Call either deleteMessage or moveMessage, depending on the target
   1147         if (mDropTargetView.mMailboxType == Mailbox.TYPE_TRASH) {
   1148             controller.deleteMessages(messageIds);
   1149         } else {
   1150             controller.moveMessages(messageIds, mDropTargetView.mMailboxId);
   1151         }
   1152         return true;
   1153     }
   1154 
   1155     @Override
   1156     public boolean onDrag(View view, DragEvent event) {
   1157         boolean result = false;
   1158         switch (event.getAction()) {
   1159             case DragEvent.ACTION_DRAG_STARTED:
   1160                 result = onDragStarted(event);
   1161                 break;
   1162             case DragEvent.ACTION_DRAG_ENTERED:
   1163                 // The drag has entered the ListView window
   1164                 if (DEBUG_DRAG_DROP) {
   1165                     Log.d(TAG, "=== Drag entered; targetId: " + mDropTargetId);
   1166                 }
   1167                 break;
   1168             case DragEvent.ACTION_DRAG_EXITED:
   1169                 // The drag has left the building
   1170                 if (DEBUG_DRAG_DROP) {
   1171                     Log.d(TAG, "=== Drag exited; targetId: " + mDropTargetId);
   1172                 }
   1173                 onDragExited();
   1174                 break;
   1175             case DragEvent.ACTION_DRAG_ENDED:
   1176                 // The drag is over
   1177                 if (DEBUG_DRAG_DROP) {
   1178                     Log.d(TAG, "=== Drag ended");
   1179                 }
   1180                 onDragEnded();
   1181                 break;
   1182             case DragEvent.ACTION_DRAG_LOCATION:
   1183                 // We're moving around within our window; handle scroll, if necessary
   1184                 onDragLocation(event);
   1185                 break;
   1186             case DragEvent.ACTION_DROP:
   1187                 // The drag item was dropped
   1188                 if (DEBUG_DRAG_DROP) {
   1189                     Log.d(TAG, "=== Drop");
   1190                 }
   1191                 result = onDrop(event);
   1192                 break;
   1193             default:
   1194                 break;
   1195         }
   1196         return result;
   1197     }
   1198 }
   1199