MailboxListFragment.java revision ad7dc464db6421d3cda62204124dedaefb0f035d
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
17package com.android.email.activity;
18
19import com.android.email.Controller;
20import com.android.email.Email;
21import com.android.email.R;
22import com.android.email.RefreshManager;
23import com.android.email.provider.EmailProvider;
24import com.android.emailcommon.Logging;
25import com.android.emailcommon.provider.EmailContent.Mailbox;
26import com.android.emailcommon.provider.EmailContent.Message;
27import com.android.emailcommon.utility.EmailAsyncTask;
28import com.android.emailcommon.utility.Utility;
29
30import android.app.Activity;
31import android.app.ListFragment;
32import android.app.LoaderManager;
33import android.app.LoaderManager.LoaderCallbacks;
34import android.content.ClipData;
35import android.content.ClipDescription;
36import android.content.Loader;
37import android.content.res.Resources;
38import android.database.Cursor;
39import android.graphics.drawable.Drawable;
40import android.net.Uri;
41import android.os.Bundle;
42import android.util.Log;
43import android.view.DragEvent;
44import android.view.LayoutInflater;
45import android.view.View;
46import android.view.View.OnDragListener;
47import android.view.ViewGroup;
48import android.widget.AdapterView;
49import android.widget.ListView;
50import android.widget.AdapterView.OnItemClickListener;
51
52import java.security.InvalidParameterException;
53
54/**
55 * This fragment presents a list of mailboxes for a given account.  The "API" includes the
56 * following elements which must be provided by the host Activity.
57 *
58 *  - call bindActivityInfo() to provide the account ID and set callbacks
59 *  - provide callbacks for onOpen and onRefresh
60 *  - pass-through implementations of onCreateContextMenu() and onContextItemSelected() (temporary)
61 *
62 * TODO Restoring ListView state -- don't do this when changing accounts
63 */
64public class MailboxListFragment extends ListFragment implements OnItemClickListener,
65        OnDragListener {
66    private static final String TAG = "MailboxListFragment";
67    private static final String BUNDLE_KEY_SELECTED_MAILBOX_ID
68            = "MailboxListFragment.state.selected_mailbox_id";
69    private static final String BUNDLE_LIST_STATE = "MailboxListFragment.state.listState";
70    private static final int LOADER_ID_MAILBOX_LIST = 1;
71    private static final boolean DEBUG_DRAG_DROP = false; // MUST NOT SUBMIT SET TO TRUE
72
73    private static final int NO_DROP_TARGET = -1;
74    // Total height of the top and bottom scroll zones, in pixels
75    private static final int SCROLL_ZONE_SIZE = 64;
76    // The amount of time to scroll by one pixel, in ms
77    private static final int SCROLL_SPEED = 4;
78
79    private RefreshManager mRefreshManager;
80
81    // UI Support
82    private Activity mActivity;
83    private MailboxesAdapter mListAdapter;
84    private Callback mCallback = EmptyCallback.INSTANCE;
85
86    private ListView mListView;
87
88    private boolean mResumed;
89
90    // Colors used for drop targets
91    private static Integer sDropTrashColor;
92    private static Drawable sDropActiveDrawable;
93
94    private long mLastLoadedAccountId = -1;
95    private long mAccountId = -1;
96    private long mSelectedMailboxId = -1;
97
98    private boolean mOpenRequested;
99
100    // True if a drag is currently in progress
101    private boolean mDragInProgress = false;
102    // The mailbox id of the dragged item's mailbox.  We use it to prevent that box from being a
103    // valid drop target
104    private long mDragItemMailboxId = -1;
105    // The adapter position that the user's finger is hovering over
106    private int mDropTargetAdapterPosition = NO_DROP_TARGET;
107    // The mailbox list item view that the user's finger is hovering over
108    private MailboxListItem mDropTargetView;
109    // Lazily instantiated height of a mailbox list item (-1 is a sentinel for 'not initialized')
110    private int mDragItemHeight = -1;
111    // True if we are currently scrolling under the drag item
112    private boolean mTargetScrolling;
113
114    private Utility.ListStateSaver mSavedListState;
115
116    private MailboxesAdapter.Callback mMailboxesAdapterCallback = new MailboxesAdapter.Callback() {
117        @Override
118        public void onBind(MailboxListItem listItem) {
119            listItem.setDropTargetBackground(mDragInProgress, mDragItemMailboxId);
120        }
121    };
122
123    /**
124     * Callback interface that owning activities must implement
125     */
126    public interface Callback {
127        /** Called when a mailbox (including combined mailbox) is selected. */
128        public void onMailboxSelected(long accountId, long mailboxId);
129
130        /** Called when an account is selected on the combined view. */
131        public void onAccountSelected(long accountId);
132
133        /**
134         * Called when the list updates to propagate the current mailbox name and the unread count
135         * for it.
136         *
137         * Note the reason why it's separated from onMailboxSelected is because this needs to be
138         * reported when the unread count changes without changing the current mailbox.
139         */
140        public void onCurrentMailboxUpdated(long mailboxId, String mailboxName, int unreadCount);
141    }
142
143    private static class EmptyCallback implements Callback {
144        public static final Callback INSTANCE = new EmptyCallback();
145        @Override public void onMailboxSelected(long accountId, long mailboxId) { }
146        @Override public void onAccountSelected(long accountId) { }
147        @Override public void onCurrentMailboxUpdated(long mailboxId, String mailboxName,
148                int unreadCount) { }
149    }
150
151    /**
152     * Called to do initial creation of a fragment.  This is called after
153     * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}.
154     */
155    @Override
156    public void onCreate(Bundle savedInstanceState) {
157        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
158            Log.d(Logging.LOG_TAG, "MailboxListFragment onCreate");
159        }
160        super.onCreate(savedInstanceState);
161
162        mActivity = getActivity();
163        mRefreshManager = RefreshManager.getInstance(mActivity);
164        mListAdapter = new MailboxFragmentAdapter(mActivity, mMailboxesAdapterCallback);
165        if (savedInstanceState != null) {
166            restoreInstanceState(savedInstanceState);
167        }
168        if (sDropTrashColor == null) {
169            Resources res = getResources();
170            sDropTrashColor = res.getColor(R.color.mailbox_drop_destructive_bg_color);
171            sDropActiveDrawable = res.getDrawable(R.drawable.list_activated_holo);
172        }
173    }
174
175    @Override
176    public View onCreateView(
177            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
178        return inflater.inflate(R.layout.mailbox_list_fragment, container, false);
179    }
180
181    @Override
182    public void onActivityCreated(Bundle savedInstanceState) {
183        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
184            Log.d(Logging.LOG_TAG, "MailboxListFragment onActivityCreated");
185        }
186        super.onActivityCreated(savedInstanceState);
187
188        mListView = getListView();
189        mListView.setOnItemClickListener(this);
190        mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
191        mListView.setOnDragListener(this);
192        registerForContextMenu(mListView);
193    }
194
195    public void setCallback(Callback callback) {
196        mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback;
197    }
198
199    private void clearContent() {
200        mLastLoadedAccountId = -1;
201        mAccountId = -1;
202        mSelectedMailboxId = -1;
203
204        mOpenRequested = false;
205        mDragInProgress = false;
206
207        stopLoader();
208        if (mListAdapter != null) {
209            mListAdapter.swapCursor(null);
210        }
211        setListShownNoAnimation(false);
212    }
213
214    /**
215     * @param accountId the account we're looking at
216     */
217    public void openMailboxes(long accountId) {
218        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
219            Log.d(Logging.LOG_TAG, "MailboxListFragment openMailboxes");
220        }
221        if (accountId == -1) {
222            throw new InvalidParameterException();
223        }
224        if (mAccountId == accountId) {
225            return;
226        }
227        clearContent();
228        mOpenRequested = true;
229        mAccountId = accountId;
230        if (mResumed) {
231            startLoading();
232        }
233    }
234
235    public void setSelectedMailbox(long mailboxId) {
236        mSelectedMailboxId = mailboxId;
237        if (mResumed) {
238            highlightSelectedMailbox(true);
239        }
240    }
241
242    /**
243     * Called when the Fragment is visible to the user.
244     */
245    @Override
246    public void onStart() {
247        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
248            Log.d(Logging.LOG_TAG, "MailboxListFragment onStart");
249        }
250        super.onStart();
251    }
252
253    /**
254     * Called when the fragment is visible to the user and actively running.
255     */
256    @Override
257    public void onResume() {
258        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
259            Log.d(Logging.LOG_TAG, "MailboxListFragment onResume");
260        }
261        super.onResume();
262        mResumed = true;
263
264        // If we're recovering from the stopped state, we don't have to reload.
265        // (when mOpenRequested = false)
266        if (mAccountId != -1 && mOpenRequested) {
267            startLoading();
268        }
269    }
270
271    @Override
272    public void onPause() {
273        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
274            Log.d(Logging.LOG_TAG, "MailboxListFragment onPause");
275        }
276        mResumed = false;
277        super.onPause();
278        mSavedListState = new Utility.ListStateSaver(getListView());
279    }
280
281    /**
282     * Called when the Fragment is no longer started.
283     */
284    @Override
285    public void onStop() {
286        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
287            Log.d(Logging.LOG_TAG, "MailboxListFragment onStop");
288        }
289        super.onStop();
290    }
291
292    /**
293     * Called when the fragment is no longer in use.
294     */
295    @Override
296    public void onDestroy() {
297        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
298            Log.d(Logging.LOG_TAG, "MailboxListFragment onDestroy");
299        }
300        super.onDestroy();
301    }
302
303    @Override
304    public void onSaveInstanceState(Bundle outState) {
305        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
306            Log.d(Logging.LOG_TAG, "MailboxListFragment onSaveInstanceState");
307        }
308        super.onSaveInstanceState(outState);
309        outState.putLong(BUNDLE_KEY_SELECTED_MAILBOX_ID, mSelectedMailboxId);
310        outState.putParcelable(BUNDLE_LIST_STATE, new Utility.ListStateSaver(getListView()));
311    }
312
313    private void restoreInstanceState(Bundle savedInstanceState) {
314        mSelectedMailboxId = savedInstanceState.getLong(BUNDLE_KEY_SELECTED_MAILBOX_ID);
315        mSavedListState = savedInstanceState.getParcelable(BUNDLE_LIST_STATE);
316    }
317
318    private void startLoading() {
319        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
320            Log.d(Logging.LOG_TAG, "MailboxListFragment startLoading");
321        }
322        mOpenRequested = false;
323        // Clear the list.  (ListFragment will show the "Loading" animation)
324        setListShown(false);
325
326        // If we've already loaded for a different account, discard the previous result and
327        // start loading again.
328        // We don't want to use restartLoader(), because if the Loader is retained, we *do* want to
329        // reuse the previous result.
330        // Also, when changing accounts, we don't preserve scroll position.
331        boolean accountChanging = false;
332        if ((mLastLoadedAccountId != -1) && (mLastLoadedAccountId != mAccountId)) {
333            accountChanging = true;
334            getLoaderManager().destroyLoader(LOADER_ID_MAILBOX_LIST);
335
336            // Also, when we're changing account, update the mailbox list if stale.
337            refreshMailboxListIfStale();
338        }
339        getLoaderManager().initLoader(LOADER_ID_MAILBOX_LIST, null,
340                new MailboxListLoaderCallbacks(accountChanging));
341    }
342
343    private void stopLoader() {
344        final LoaderManager lm = getLoaderManager();
345        lm.destroyLoader(LOADER_ID_MAILBOX_LIST);
346    }
347
348    private class MailboxListLoaderCallbacks implements LoaderCallbacks<Cursor> {
349        private boolean mAccountChanging;
350
351        public MailboxListLoaderCallbacks(boolean accountChanging) {
352            mAccountChanging = accountChanging;
353        }
354
355        @Override
356        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
357            if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
358                Log.d(Logging.LOG_TAG, "MailboxListFragment onCreateLoader");
359            }
360            return MailboxFragmentAdapter.createLoader(getActivity(), mAccountId);
361        }
362
363        @Override
364        public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
365            if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
366                Log.d(Logging.LOG_TAG, "MailboxListFragment onLoadFinished");
367            }
368            mLastLoadedAccountId = mAccountId;
369
370            // Save list view state (primarily scroll position)
371            final ListView lv = getListView();
372            final Utility.ListStateSaver lss;
373            if (mAccountChanging) {
374                lss = null; // Don't preserve list state
375            } else if (mSavedListState != null) {
376                lss = mSavedListState;
377                mSavedListState = null;
378            } else {
379                lss = new Utility.ListStateSaver(lv);
380            }
381
382            if (cursor.getCount() == 0) {
383                // If there's no row, don't set it to the ListView.
384                // Instead use setListShown(false) to make ListFragment show progress icon.
385                mListAdapter.swapCursor(null);
386                setListShown(false);
387            } else {
388                // Set the adapter.
389                mListAdapter.swapCursor(cursor);
390                setListAdapter(mListAdapter);
391                setListShown(true);
392
393                // We want to make selection visible only when account is changing..
394                // i.e. Refresh caused by content changed events shouldn't scroll the list.
395                highlightSelectedMailbox(mAccountChanging);
396            }
397
398            // Restore the state
399            if (lss != null) {
400                lss.restore(lv);
401            }
402
403            // Clear this for next reload triggered by content changed events.
404            mAccountChanging = false;
405        }
406
407        @Override
408        public void onLoaderReset(Loader<Cursor> loader) {
409            mListAdapter.swapCursor(null);
410        }
411    }
412
413    public void onItemClick(AdapterView<?> parent, View view, int position,
414            long idDontUseIt /* see MailboxesAdapter */ ) {
415        final long id = mListAdapter.getId(position);
416        if (mListAdapter.isAccountRow(position)) {
417            mCallback.onAccountSelected(id);
418        } else {
419            mCallback.onMailboxSelected(mAccountId, id);
420        }
421    }
422
423    public void onRefresh() {
424        if (mAccountId != -1) {
425            mRefreshManager.refreshMailboxList(mAccountId);
426        }
427    }
428
429    private void refreshMailboxListIfStale() {
430        if (mRefreshManager.isMailboxListStale(mAccountId)) {
431            mRefreshManager.refreshMailboxList(mAccountId);
432        }
433    }
434
435    /**
436     * Highlight the selected mailbox.
437     */
438    private void highlightSelectedMailbox(boolean ensureSelectionVisible) {
439        String mailboxName = "";
440        int unreadCount = 0;
441        if (mSelectedMailboxId == -1) {
442            // No mailbox selected
443            mListView.clearChoices();
444        } else {
445            final int count = mListView.getCount();
446            for (int i = 0; i < count; i++) {
447                if (mListAdapter.getId(i) != mSelectedMailboxId) {
448                    continue;
449                }
450                mListView.setItemChecked(i, true);
451                if (ensureSelectionVisible) {
452                    Utility.listViewSmoothScrollToPosition(getActivity(), mListView, i);
453                }
454                mailboxName = mListAdapter.getDisplayName(mActivity, i);
455                unreadCount = mListAdapter.getUnreadCount(i);
456                break;
457            }
458        }
459        mCallback.onCurrentMailboxUpdated(mSelectedMailboxId, mailboxName, unreadCount);
460    }
461
462    // Drag & Drop handling
463
464    /**
465     * Update all of the list's child views with the proper target background (for now, orange if
466     * a valid target, except red if the trash; standard background otherwise)
467     */
468    private void updateChildViews() {
469        int itemCount = mListView.getChildCount();
470        // Lazily initialize the height of our list items
471        if (itemCount > 0 && mDragItemHeight < 0) {
472            mDragItemHeight = mListView.getChildAt(0).getHeight();
473        }
474        for (int i = 0; i < itemCount; i++) {
475            MailboxListItem item = (MailboxListItem)mListView.getChildAt(i);
476            item.setDropTargetBackground(mDragInProgress, mDragItemMailboxId);
477        }
478    }
479
480    /**
481     * Called when our ListView gets a DRAG_EXITED event
482     */
483    private void onDragExited() {
484        // Reset the background of the current target
485        if (mDropTargetAdapterPosition != NO_DROP_TARGET) {
486            mDropTargetView.setDropTargetBackground(mDragInProgress, mDragItemMailboxId);
487            mDropTargetAdapterPosition = NO_DROP_TARGET;
488        }
489        stopScrolling();
490    }
491
492    /**
493     * Called while dragging;  highlight possible drop targets, and autoscroll the list.
494     */
495    private void onDragLocation(DragEvent event) {
496        // The drag is somewhere in the ListView
497        if (mDragItemHeight <= 0) {
498            // This shouldn't be possible, but avoid NPE
499            return;
500        }
501        // Find out which item we're in and highlight as appropriate
502        int rawTouchY = (int)event.getY();
503        int offset = 0;
504        if (mListView.getCount() > 0) {
505            offset = mListView.getChildAt(0).getTop();
506        }
507        int targetScreenPosition = (rawTouchY - offset) / mDragItemHeight;
508        int firstVisibleItem = mListView.getFirstVisiblePosition();
509        int targetAdapterPosition = firstVisibleItem + targetScreenPosition;
510        if (targetAdapterPosition != mDropTargetAdapterPosition) {
511            if (DEBUG_DRAG_DROP) {
512                Log.d(TAG, "========== DROP TARGET " + mDropTargetAdapterPosition + " -> " +
513                        targetAdapterPosition);
514            }
515            // Unhighlight the current target, if we've got one
516            if (mDropTargetAdapterPosition != NO_DROP_TARGET) {
517                mDropTargetView.setDropTargetBackground(true, mDragItemMailboxId);
518            }
519            // Get the new target mailbox view
520            MailboxListItem newTarget =
521                (MailboxListItem)mListView.getChildAt(targetScreenPosition);
522            // This can be null due to a bug in the framework (checking on that)
523            // In any event, we're no longer dragging in the list view if newTarget is null
524            if (newTarget == null) {
525                if (DEBUG_DRAG_DROP) {
526                    Log.d(TAG, "========== WTF??? DRAG EXITED");
527                }
528                onDragExited();
529                return;
530            } else if (newTarget.mMailboxType == Mailbox.TYPE_TRASH) {
531                if (DEBUG_DRAG_DROP) {
532                    Log.d("onDragLocation", "=== Mailbox " + newTarget.mMailboxId + " TRASH");
533                }
534                newTarget.setBackgroundColor(sDropTrashColor);
535            } else if (newTarget.isDropTarget(mDragItemMailboxId)) {
536                if (DEBUG_DRAG_DROP) {
537                    Log.d("onDragLocation", "=== Mailbox " + newTarget.mMailboxId + " TARGET");
538                }
539                newTarget.setBackgroundDrawable(sDropActiveDrawable);
540            } else {
541                if (DEBUG_DRAG_DROP) {
542                    Log.d("onDragLocation", "=== Mailbox " + newTarget.mMailboxId + " (CALL)");
543                }
544                targetAdapterPosition = NO_DROP_TARGET;
545                newTarget.setDropTargetBackground(true, mDragItemMailboxId);
546            }
547            // Save away our current position and view
548            mDropTargetAdapterPosition = targetAdapterPosition;
549            mDropTargetView = newTarget;
550        }
551
552        // This is a quick-and-dirty implementation of drag-under-scroll; something like this
553        // should eventually find its way into the framework
554        int scrollDiff = rawTouchY - (mListView.getHeight() - SCROLL_ZONE_SIZE);
555        boolean scrollDown = (scrollDiff > 0);
556        boolean scrollUp = (SCROLL_ZONE_SIZE > rawTouchY);
557        if (!mTargetScrolling && scrollDown) {
558            int itemsToScroll = mListView.getCount() - targetAdapterPosition;
559            int pixelsToScroll = (itemsToScroll + 1) * mDragItemHeight;
560            mListView.smoothScrollBy(pixelsToScroll, pixelsToScroll * SCROLL_SPEED);
561            if (DEBUG_DRAG_DROP) {
562                Log.d(TAG, "========== START TARGET SCROLLING DOWN");
563            }
564            mTargetScrolling = true;
565        } else if (!mTargetScrolling && scrollUp) {
566            int pixelsToScroll = (firstVisibleItem + 1) * mDragItemHeight;
567            mListView.smoothScrollBy(-pixelsToScroll, pixelsToScroll * SCROLL_SPEED);
568            if (DEBUG_DRAG_DROP) {
569                Log.d(TAG, "========== START TARGET SCROLLING UP");
570            }
571            mTargetScrolling = true;
572        } else if (!scrollUp && !scrollDown) {
573            stopScrolling();
574        }
575    }
576
577    /**
578     * Indicate that scrolling has stopped
579     */
580    private void stopScrolling() {
581        if (mTargetScrolling) {
582            mTargetScrolling = false;
583            if (DEBUG_DRAG_DROP) {
584                Log.d(TAG, "========== STOP TARGET SCROLLING");
585            }
586            // Stop the scrolling
587            mListView.smoothScrollBy(0, 0);
588        }
589    }
590
591    private void onDragEnded() {
592        if (mDragInProgress) {
593            mDragInProgress = false;
594            // Reenable updates to the view and redraw (in case it changed)
595            MailboxesAdapter.enableUpdates(true);
596            mListAdapter.notifyDataSetChanged();
597            // Stop highlighting targets
598            updateChildViews();
599            // Stop any scrolling that was going on
600            stopScrolling();
601        }
602    }
603
604    private boolean onDragStarted(DragEvent event) {
605        // We handle dropping of items with our email mime type
606        // If the mime type has a mailbox id appended, that is the mailbox of the item
607        // being draged
608        ClipDescription description = event.getClipDescription();
609        int mimeTypeCount = description.getMimeTypeCount();
610        for (int i = 0; i < mimeTypeCount; i++) {
611            String mimeType = description.getMimeType(i);
612            if (mimeType.startsWith(EmailProvider.EMAIL_MESSAGE_MIME_TYPE)) {
613                if (DEBUG_DRAG_DROP) {
614                    Log.d(TAG, "========== DRAG STARTED");
615                }
616                mDragItemMailboxId = -1;
617                // See if we find a mailbox id here
618                int dash = mimeType.lastIndexOf('-');
619                if (dash > 0) {
620                    try {
621                        mDragItemMailboxId = Long.parseLong(mimeType.substring(dash + 1));
622                    } catch (NumberFormatException e) {
623                        // Ignore; we just won't know the mailbox
624                    }
625                }
626                mDragInProgress = true;
627                // Stop the list from updating
628                MailboxesAdapter.enableUpdates(false);
629                // Update the backgrounds of our child views to highlight drop targets
630                updateChildViews();
631                return true;
632            }
633        }
634        return false;
635    }
636
637    private boolean onDrop(DragEvent event) {
638        stopScrolling();
639        // If we're not on a target, we're done
640        if (mDropTargetAdapterPosition == NO_DROP_TARGET) return false;
641        final Controller controller = Controller.getInstance(mActivity);
642        ClipData clipData = event.getClipData();
643        int count = clipData.getItemCount();
644        if (DEBUG_DRAG_DROP) {
645            Log.d(TAG, "Received a drop of " + count + " items.");
646        }
647        // Extract the messageId's to move from the ClipData (set up in MessageListItem)
648        final long[] messageIds = new long[count];
649        for (int i = 0; i < count; i++) {
650            Uri uri = clipData.getItemAt(i).getUri();
651            String msgNum = uri.getPathSegments().get(1);
652            long id = Long.parseLong(msgNum);
653            messageIds[i] = id;
654        }
655        // Call either deleteMessage or moveMessage, depending on the target
656        EmailAsyncTask.runAsyncSerial(new Runnable() {
657            @Override
658            public void run() {
659                if (mDropTargetView.mMailboxType == Mailbox.TYPE_TRASH) {
660                    for (long messageId: messageIds) {
661                        // TODO Get this off UI thread (put in clip)
662                        Message msg = Message.restoreMessageWithId(mActivity, messageId);
663                        if (msg != null) {
664                            controller.deleteMessage(messageId, msg.mAccountKey);
665                        }
666                    }
667                } else {
668                    controller.moveMessage(messageIds, mDropTargetView.mMailboxId);
669                }
670            }
671        });
672        return true;
673    }
674
675    @Override
676    public boolean onDrag(View view, DragEvent event) {
677        boolean result = false;
678        switch (event.getAction()) {
679            case DragEvent.ACTION_DRAG_STARTED:
680                result = onDragStarted(event);
681                break;
682            case DragEvent.ACTION_DRAG_ENTERED:
683                // The drag has entered the ListView window
684                if (DEBUG_DRAG_DROP) {
685                    Log.d(TAG, "========== DRAG ENTERED (target = " + mDropTargetAdapterPosition +
686                    ")");
687                }
688                break;
689            case DragEvent.ACTION_DRAG_EXITED:
690                // The drag has left the building
691                if (DEBUG_DRAG_DROP) {
692                    Log.d(TAG, "========== DRAG EXITED (target = " + mDropTargetAdapterPosition +
693                            ")");
694                }
695                onDragExited();
696                break;
697            case DragEvent.ACTION_DRAG_ENDED:
698                // The drag is over
699                if (DEBUG_DRAG_DROP) {
700                    Log.d(TAG, "========== DRAG ENDED");
701                }
702                onDragEnded();
703                break;
704            case DragEvent.ACTION_DRAG_LOCATION:
705                // We're moving around within our window; handle scroll, if necessary
706                onDragLocation(event);
707                break;
708            case DragEvent.ACTION_DROP:
709                // The drag item was dropped
710                if (DEBUG_DRAG_DROP) {
711                    Log.d(TAG, "========== DROP");
712                }
713                result = onDrop(event);
714                break;
715            default:
716                break;
717        }
718        return result;
719    }
720}
721