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