Home | History | Annotate | Download | only in launcher2
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.launcher2;
     18 
     19 import android.content.Context;
     20 import android.graphics.Bitmap;
     21 import android.graphics.Point;
     22 import android.graphics.Rect;
     23 import android.os.Handler;
     24 import android.os.IBinder;
     25 import android.os.Vibrator;
     26 import android.util.Log;
     27 import android.view.KeyEvent;
     28 import android.view.MotionEvent;
     29 import android.view.View;
     30 import android.view.ViewConfiguration;
     31 import android.view.inputmethod.InputMethodManager;
     32 
     33 import com.android.launcher.R;
     34 
     35 import java.util.ArrayList;
     36 
     37 /**
     38  * Class for initiating a drag within a view or across multiple views.
     39  */
     40 public class DragController {
     41     @SuppressWarnings({"UnusedDeclaration"})
     42     private static final String TAG = "Launcher.DragController";
     43 
     44     /** Indicates the drag is a move.  */
     45     public static int DRAG_ACTION_MOVE = 0;
     46 
     47     /** Indicates the drag is a copy.  */
     48     public static int DRAG_ACTION_COPY = 1;
     49 
     50     private static final int SCROLL_DELAY = 600;
     51     private static final int VIBRATE_DURATION = 35;
     52 
     53     private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
     54 
     55     private static final int SCROLL_OUTSIDE_ZONE = 0;
     56     private static final int SCROLL_WAITING_IN_ZONE = 1;
     57 
     58     static final int SCROLL_NONE = -1;
     59     static final int SCROLL_LEFT = 0;
     60     static final int SCROLL_RIGHT = 1;
     61 
     62     private Launcher mLauncher;
     63     private Handler mHandler;
     64     private final Vibrator mVibrator = new Vibrator();
     65 
     66     // temporaries to avoid gc thrash
     67     private Rect mRectTemp = new Rect();
     68     private final int[] mCoordinatesTemp = new int[2];
     69 
     70     /** Whether or not we're dragging. */
     71     private boolean mDragging;
     72 
     73     /** X coordinate of the down event. */
     74     private int mMotionDownX;
     75 
     76     /** Y coordinate of the down event. */
     77     private int mMotionDownY;
     78 
     79     /** the area at the edge of the screen that makes the workspace go left
     80      *   or right while you're dragging.
     81      */
     82     private int mScrollZone;
     83 
     84     private DropTarget.DragObject mDragObject;
     85 
     86     /** Who can receive drop events */
     87     private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>();
     88 
     89     private ArrayList<DragListener> mListeners = new ArrayList<DragListener>();
     90 
     91     /** The window token used as the parent for the DragView. */
     92     private IBinder mWindowToken;
     93 
     94     /** The view that will be scrolled when dragging to the left and right edges of the screen. */
     95     private View mScrollView;
     96 
     97     private View mMoveTarget;
     98 
     99     private DragScroller mDragScroller;
    100     private int mScrollState = SCROLL_OUTSIDE_ZONE;
    101     private ScrollRunnable mScrollRunnable = new ScrollRunnable();
    102 
    103     private DropTarget mLastDropTarget;
    104 
    105     private InputMethodManager mInputMethodManager;
    106 
    107     private int mLastTouch[] = new int[2];
    108     private int mDistanceSinceScroll = 0;
    109 
    110     private int mTmpPoint[] = new int[2];
    111     private Rect mDragLayerRect = new Rect();
    112 
    113     /**
    114      * Interface to receive notifications when a drag starts or stops
    115      */
    116     interface DragListener {
    117 
    118         /**
    119          * A drag has begun
    120          *
    121          * @param source An object representing where the drag originated
    122          * @param info The data associated with the object that is being dragged
    123          * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE}
    124          *        or {@link DragController#DRAG_ACTION_COPY}
    125          */
    126         void onDragStart(DragSource source, Object info, int dragAction);
    127 
    128         /**
    129          * The drag has ended
    130          */
    131         void onDragEnd();
    132     }
    133 
    134     /**
    135      * Used to create a new DragLayer from XML.
    136      *
    137      * @param context The application's context.
    138      */
    139     public DragController(Launcher launcher) {
    140         mLauncher = launcher;
    141         mHandler = new Handler();
    142         mScrollZone = launcher.getResources().getDimensionPixelSize(R.dimen.scroll_zone);
    143     }
    144 
    145     public boolean dragging() {
    146         return mDragging;
    147     }
    148 
    149     /**
    150      * Starts a drag.
    151      *
    152      * @param v The view that is being dragged
    153      * @param source An object representing where the drag originated
    154      * @param dragInfo The data associated with the object that is being dragged
    155      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
    156      *        {@link #DRAG_ACTION_COPY}
    157      */
    158     public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) {
    159         startDrag(v, source, dragInfo, dragAction, null);
    160     }
    161 
    162     /**
    163      * Starts a drag.
    164      *
    165      * @param v The view that is being dragged
    166      * @param source An object representing where the drag originated
    167      * @param dragInfo The data associated with the object that is being dragged
    168      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
    169      *        {@link #DRAG_ACTION_COPY}
    170      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
    171      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
    172      */
    173     public void startDrag(View v, DragSource source, Object dragInfo, int dragAction,
    174             Rect dragRegion) {
    175         Bitmap b = getViewBitmap(v);
    176 
    177         if (b == null) {
    178             // out of memory?
    179             return;
    180         }
    181 
    182         int[] loc = mCoordinatesTemp;
    183         mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
    184         int dragLayerX = loc[0];
    185         int dragLayerY = loc[1];
    186 
    187         startDrag(b, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion);
    188         b.recycle();
    189 
    190         if (dragAction == DRAG_ACTION_MOVE) {
    191             v.setVisibility(View.GONE);
    192         }
    193     }
    194 
    195     /**
    196      * Starts a drag.
    197      *
    198      * @param v The view that is being dragged
    199      * @param bmp The bitmap that represents the view being dragged
    200      * @param source An object representing where the drag originated
    201      * @param dragInfo The data associated with the object that is being dragged
    202      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
    203      *        {@link #DRAG_ACTION_COPY}
    204      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
    205      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
    206      */
    207     public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction,
    208             Rect dragRegion) {
    209         int[] loc = mCoordinatesTemp;
    210         mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
    211         int dragLayerX = loc[0];
    212         int dragLayerY = loc[1];
    213 
    214         startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion);
    215 
    216         if (dragAction == DRAG_ACTION_MOVE) {
    217             v.setVisibility(View.GONE);
    218         }
    219     }
    220 
    221     /**
    222      * Starts a drag.
    223      *
    224      * @param b The bitmap to display as the drag image.  It will be re-scaled to the
    225      *          enlarged size.
    226      * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
    227      * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
    228      * @param source An object representing where the drag originated
    229      * @param dragInfo The data associated with the object that is being dragged
    230      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
    231      *        {@link #DRAG_ACTION_COPY}
    232      */
    233     public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
    234             DragSource source, Object dragInfo, int dragAction) {
    235         startDrag(b, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, null);
    236     }
    237 
    238     /**
    239      * Starts a drag.
    240      *
    241      * @param b The bitmap to display as the drag image.  It will be re-scaled to the
    242      *          enlarged size.
    243      * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
    244      * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
    245      * @param source An object representing where the drag originated
    246      * @param dragInfo The data associated with the object that is being dragged
    247      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
    248      *        {@link #DRAG_ACTION_COPY}
    249      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
    250      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
    251      */
    252     public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
    253             DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion) {
    254         if (PROFILE_DRAWING_DURING_DRAG) {
    255             android.os.Debug.startMethodTracing("Launcher");
    256         }
    257 
    258         // Hide soft keyboard, if visible
    259         if (mInputMethodManager == null) {
    260             mInputMethodManager = (InputMethodManager)
    261                     mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
    262         }
    263         mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
    264 
    265         for (DragListener listener : mListeners) {
    266             listener.onDragStart(source, dragInfo, dragAction);
    267         }
    268 
    269         final int registrationX = mMotionDownX - dragLayerX;
    270         final int registrationY = mMotionDownY - dragLayerY;
    271 
    272         final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
    273         final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
    274 
    275         mDragging = true;
    276 
    277         mDragObject = new DropTarget.DragObject();
    278 
    279         mDragObject.dragComplete = false;
    280         mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
    281         mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
    282         mDragObject.dragSource = source;
    283         mDragObject.dragInfo = dragInfo;
    284 
    285         mVibrator.vibrate(VIBRATE_DURATION);
    286 
    287         final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
    288                 registrationY, 0, 0, b.getWidth(), b.getHeight());
    289 
    290         if (dragOffset != null) {
    291             dragView.setDragVisualizeOffset(new Point(dragOffset));
    292         }
    293         if (dragRegion != null) {
    294             dragView.setDragRegion(new Rect(dragRegion));
    295         }
    296 
    297         dragView.show(mMotionDownX, mMotionDownY);
    298         handleMoveEvent(mMotionDownX, mMotionDownY);
    299     }
    300 
    301     /**
    302      * Draw the view into a bitmap.
    303      */
    304     Bitmap getViewBitmap(View v) {
    305         v.clearFocus();
    306         v.setPressed(false);
    307 
    308         boolean willNotCache = v.willNotCacheDrawing();
    309         v.setWillNotCacheDrawing(false);
    310 
    311         // Reset the drawing cache background color to fully transparent
    312         // for the duration of this operation
    313         int color = v.getDrawingCacheBackgroundColor();
    314         v.setDrawingCacheBackgroundColor(0);
    315         float alpha = v.getAlpha();
    316         v.setAlpha(1.0f);
    317 
    318         if (color != 0) {
    319             v.destroyDrawingCache();
    320         }
    321         v.buildDrawingCache();
    322         Bitmap cacheBitmap = v.getDrawingCache();
    323         if (cacheBitmap == null) {
    324             Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException());
    325             return null;
    326         }
    327 
    328         Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
    329 
    330         // Restore the view
    331         v.destroyDrawingCache();
    332         v.setAlpha(alpha);
    333         v.setWillNotCacheDrawing(willNotCache);
    334         v.setDrawingCacheBackgroundColor(color);
    335 
    336         return bitmap;
    337     }
    338 
    339     /**
    340      * Call this from a drag source view like this:
    341      *
    342      * <pre>
    343      *  @Override
    344      *  public boolean dispatchKeyEvent(KeyEvent event) {
    345      *      return mDragController.dispatchKeyEvent(this, event)
    346      *              || super.dispatchKeyEvent(event);
    347      * </pre>
    348      */
    349     @SuppressWarnings({"UnusedDeclaration"})
    350     public boolean dispatchKeyEvent(KeyEvent event) {
    351         return mDragging;
    352     }
    353 
    354     public boolean isDragging() {
    355         return mDragging;
    356     }
    357 
    358     /**
    359      * Stop dragging without dropping.
    360      */
    361     public void cancelDrag() {
    362         if (mDragging) {
    363             if (mLastDropTarget != null) {
    364                 mLastDropTarget.onDragExit(mDragObject);
    365             }
    366             mDragObject.cancelled = true;
    367             mDragObject.dragComplete = true;
    368             mDragObject.dragSource.onDropCompleted(null, mDragObject, false);
    369         }
    370         endDrag();
    371     }
    372     public void onAppsRemoved(ArrayList<ApplicationInfo> apps, Context context) {
    373         // Cancel the current drag if we are removing an app that we are dragging
    374         if (mDragObject != null) {
    375             Object rawDragInfo = mDragObject.dragInfo;
    376             if (rawDragInfo instanceof ShortcutInfo) {
    377                 ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo;
    378                 for (ApplicationInfo info : apps) {
    379                     if (dragInfo.intent.getComponent().equals(info.intent.getComponent())) {
    380                         cancelDrag();
    381                         return;
    382                     }
    383                 }
    384             }
    385         }
    386     }
    387 
    388     private void endDrag() {
    389         if (mDragging) {
    390             mDragging = false;
    391             for (DragListener listener : mListeners) {
    392                 listener.onDragEnd();
    393             }
    394             if (mDragObject.dragView != null) {
    395                 mDragObject.dragView.remove();
    396                 mDragObject.dragView = null;
    397             }
    398         }
    399     }
    400 
    401     /**
    402      * Clamps the position to the drag layer bounds.
    403      */
    404     private int[] getClampedDragLayerPos(float x, float y) {
    405         mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect);
    406         mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1));
    407         mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1));
    408         return mTmpPoint;
    409     }
    410 
    411     /**
    412      * Call this from a drag source view.
    413      */
    414     public boolean onInterceptTouchEvent(MotionEvent ev) {
    415         if (false) {
    416             Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging="
    417                     + mDragging);
    418         }
    419         final int action = ev.getAction();
    420 
    421         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
    422         final int dragLayerX = dragLayerPos[0];
    423         final int dragLayerY = dragLayerPos[1];
    424 
    425         switch (action) {
    426             case MotionEvent.ACTION_MOVE:
    427                 break;
    428             case MotionEvent.ACTION_DOWN:
    429                 // Remember location of down touch
    430                 mMotionDownX = dragLayerX;
    431                 mMotionDownY = dragLayerY;
    432                 mLastDropTarget = null;
    433                 break;
    434             case MotionEvent.ACTION_UP:
    435                 if (mDragging) {
    436                     drop(dragLayerX, dragLayerY);
    437                 }
    438                 endDrag();
    439                 break;
    440             case MotionEvent.ACTION_CANCEL:
    441                 cancelDrag();
    442                 break;
    443         }
    444 
    445         return mDragging;
    446     }
    447 
    448     /**
    449      * Sets the view that should handle move events.
    450      */
    451     void setMoveTarget(View view) {
    452         mMoveTarget = view;
    453     }
    454 
    455     public boolean dispatchUnhandledMove(View focused, int direction) {
    456         return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
    457     }
    458 
    459     private void handleMoveEvent(int x, int y) {
    460         mDragObject.dragView.move(x, y);
    461 
    462         // Drop on someone?
    463         final int[] coordinates = mCoordinatesTemp;
    464         DropTarget dropTarget = findDropTarget(x, y, coordinates);
    465         mDragObject.x = coordinates[0];
    466         mDragObject.y = coordinates[1];
    467         if (dropTarget != null) {
    468             DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject);
    469             if (delegate != null) {
    470                 dropTarget = delegate;
    471             }
    472 
    473             if (mLastDropTarget != dropTarget) {
    474                 if (mLastDropTarget != null) {
    475                     mLastDropTarget.onDragExit(mDragObject);
    476                 }
    477                 dropTarget.onDragEnter(mDragObject);
    478             }
    479             dropTarget.onDragOver(mDragObject);
    480         } else {
    481             if (mLastDropTarget != null) {
    482                 mLastDropTarget.onDragExit(mDragObject);
    483             }
    484         }
    485         mLastDropTarget = dropTarget;
    486 
    487         // After a scroll, the touch point will still be in the scroll region.
    488         // Rather than scrolling immediately, require a bit of twiddling to scroll again
    489         final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
    490         mDistanceSinceScroll +=
    491             Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
    492         mLastTouch[0] = x;
    493         mLastTouch[1] = y;
    494 
    495         if (x < mScrollZone) {
    496             if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
    497                 mScrollState = SCROLL_WAITING_IN_ZONE;
    498                 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) {
    499                     mScrollRunnable.setDirection(SCROLL_LEFT);
    500                     mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
    501                 }
    502             }
    503         } else if (x > mScrollView.getWidth() - mScrollZone) {
    504             if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
    505                 mScrollState = SCROLL_WAITING_IN_ZONE;
    506                 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) {
    507                     mScrollRunnable.setDirection(SCROLL_RIGHT);
    508                     mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
    509                 }
    510             }
    511         } else {
    512             if (mScrollState == SCROLL_WAITING_IN_ZONE) {
    513                 mScrollState = SCROLL_OUTSIDE_ZONE;
    514                 mScrollRunnable.setDirection(SCROLL_RIGHT);
    515                 mHandler.removeCallbacks(mScrollRunnable);
    516                 mDragScroller.onExitScrollArea();
    517             }
    518         }
    519     }
    520 
    521     /**
    522      * Call this from a drag source view.
    523      */
    524     public boolean onTouchEvent(MotionEvent ev) {
    525         if (!mDragging) {
    526             return false;
    527         }
    528 
    529         final int action = ev.getAction();
    530         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
    531         final int dragLayerX = dragLayerPos[0];
    532         final int dragLayerY = dragLayerPos[1];
    533 
    534         switch (action) {
    535         case MotionEvent.ACTION_DOWN:
    536             // Remember where the motion event started
    537             mMotionDownX = dragLayerX;
    538             mMotionDownY = dragLayerY;
    539 
    540             if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
    541                 mScrollState = SCROLL_WAITING_IN_ZONE;
    542                 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
    543             } else {
    544                 mScrollState = SCROLL_OUTSIDE_ZONE;
    545             }
    546             break;
    547         case MotionEvent.ACTION_MOVE:
    548             handleMoveEvent(dragLayerX, dragLayerY);
    549             break;
    550         case MotionEvent.ACTION_UP:
    551             // Ensure that we've processed a move event at the current pointer location.
    552             handleMoveEvent(dragLayerX, dragLayerY);
    553 
    554             mHandler.removeCallbacks(mScrollRunnable);
    555             if (mDragging) {
    556                 drop(dragLayerX, dragLayerY);
    557             }
    558             endDrag();
    559             break;
    560         case MotionEvent.ACTION_CANCEL:
    561             cancelDrag();
    562             break;
    563         }
    564 
    565         return true;
    566     }
    567 
    568     private void drop(float x, float y) {
    569         final int[] coordinates = mCoordinatesTemp;
    570         final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
    571 
    572         mDragObject.x = coordinates[0];
    573         mDragObject.y = coordinates[1];
    574         boolean accepted = false;
    575         if (dropTarget != null) {
    576             mDragObject.dragComplete = true;
    577             dropTarget.onDragExit(mDragObject);
    578             if (dropTarget.acceptDrop(mDragObject)) {
    579                 dropTarget.onDrop(mDragObject);
    580                 accepted = true;
    581             }
    582         }
    583         mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, accepted);
    584     }
    585 
    586     private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
    587         final Rect r = mRectTemp;
    588 
    589         final ArrayList<DropTarget> dropTargets = mDropTargets;
    590         final int count = dropTargets.size();
    591         for (int i=count-1; i>=0; i--) {
    592             DropTarget target = dropTargets.get(i);
    593             if (!target.isDropEnabled())
    594                 continue;
    595 
    596             target.getHitRect(r);
    597 
    598             // Convert the hit rect to DragLayer coordinates
    599             target.getLocationInDragLayer(dropCoordinates);
    600             r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop());
    601 
    602             mDragObject.x = x;
    603             mDragObject.y = y;
    604             if (r.contains(x, y)) {
    605                 DropTarget delegate = target.getDropTargetDelegate(mDragObject);
    606                 if (delegate != null) {
    607                     target = delegate;
    608                     target.getLocationInDragLayer(dropCoordinates);
    609                 }
    610 
    611                 // Make dropCoordinates relative to the DropTarget
    612                 dropCoordinates[0] = x - dropCoordinates[0];
    613                 dropCoordinates[1] = y - dropCoordinates[1];
    614 
    615                 return target;
    616             }
    617         }
    618         return null;
    619     }
    620 
    621     public void setDragScoller(DragScroller scroller) {
    622         mDragScroller = scroller;
    623     }
    624 
    625     public void setWindowToken(IBinder token) {
    626         mWindowToken = token;
    627     }
    628 
    629     /**
    630      * Sets the drag listner which will be notified when a drag starts or ends.
    631      */
    632     public void addDragListener(DragListener l) {
    633         mListeners.add(l);
    634     }
    635 
    636     /**
    637      * Remove a previously installed drag listener.
    638      */
    639     public void removeDragListener(DragListener l) {
    640         mListeners.remove(l);
    641     }
    642 
    643     /**
    644      * Add a DropTarget to the list of potential places to receive drop events.
    645      */
    646     public void addDropTarget(DropTarget target) {
    647         mDropTargets.add(target);
    648     }
    649 
    650     /**
    651      * Don't send drop events to <em>target</em> any more.
    652      */
    653     public void removeDropTarget(DropTarget target) {
    654         mDropTargets.remove(target);
    655     }
    656 
    657     /**
    658      * Set which view scrolls for touch events near the edge of the screen.
    659      */
    660     public void setScrollView(View v) {
    661         mScrollView = v;
    662     }
    663 
    664     DragView getDragView() {
    665         return mDragObject.dragView;
    666     }
    667 
    668     private class ScrollRunnable implements Runnable {
    669         private int mDirection;
    670 
    671         ScrollRunnable() {
    672         }
    673 
    674         public void run() {
    675             if (mDragScroller != null) {
    676                 if (mDirection == SCROLL_LEFT) {
    677                     mDragScroller.scrollLeft();
    678                 } else {
    679                     mDragScroller.scrollRight();
    680                 }
    681                 mScrollState = SCROLL_OUTSIDE_ZONE;
    682                 mDistanceSinceScroll = 0;
    683                 mDragScroller.onExitScrollArea();
    684             }
    685         }
    686 
    687         void setDirection(int direction) {
    688             mDirection = direction;
    689         }
    690     }
    691 }
    692