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