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