• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Rect;
22 import android.graphics.RectF;
23 import android.os.IBinder;
24 import android.os.Handler;
25 import android.os.Vibrator;
26 import android.util.DisplayMetrics;
27 import android.util.Log;
28 import android.view.View;
29 import android.view.KeyEvent;
30 import android.view.MotionEvent;
31 import android.view.WindowManager;
32 import android.view.inputmethod.InputMethodManager;
33 
34 import java.util.ArrayList;
35 
36 import com.android.launcher.R;
37 
38 /**
39  * Class for initiating a drag within a view or across multiple views.
40  */
41 public class DragController {
42     @SuppressWarnings({"UnusedDeclaration"})
43     private static final String TAG = "Launcher.DragController";
44 
45     /** Indicates the drag is a move.  */
46     public static int DRAG_ACTION_MOVE = 0;
47 
48     /** Indicates the drag is a copy.  */
49     public static int DRAG_ACTION_COPY = 1;
50 
51     private static final int SCROLL_DELAY = 600;
52     private static final int VIBRATE_DURATION = 35;
53 
54     private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
55 
56     private static final int SCROLL_OUTSIDE_ZONE = 0;
57     private static final int SCROLL_WAITING_IN_ZONE = 1;
58 
59     private static final int SCROLL_LEFT = 0;
60     private static final int SCROLL_RIGHT = 1;
61 
62     private Context mContext;
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 float mMotionDownX;
75 
76     /** Y coordinate of the down event. */
77     private float mMotionDownY;
78 
79     /** Info about the screen for clamping. */
80     private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
81 
82     /** Original view that is being dragged.  */
83     private View mOriginator;
84 
85     /** X offset from the upper-left corner of the cell to where we touched.  */
86     private float mTouchOffsetX;
87 
88     /** Y offset from the upper-left corner of the cell to where we touched.  */
89     private float mTouchOffsetY;
90 
91     /** the area at the edge of the screen that makes the workspace go left
92      *   or right while you're dragging.
93      */
94     private int mScrollZone;
95 
96     /** Where the drag originated */
97     private DragSource mDragSource;
98 
99     /** The data associated with the object being dragged */
100     private Object mDragInfo;
101 
102     /** The view that moves around while you drag.  */
103     private DragView mDragView;
104 
105     /** Who can receive drop events */
106     private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>();
107 
108     private DragListener mListener;
109 
110     /** The window token used as the parent for the DragView. */
111     private IBinder mWindowToken;
112 
113     /** The view that will be scrolled when dragging to the left and right edges of the screen. */
114     private View mScrollView;
115 
116     private View mMoveTarget;
117 
118     private DragScroller mDragScroller;
119     private int mScrollState = SCROLL_OUTSIDE_ZONE;
120     private ScrollRunnable mScrollRunnable = new ScrollRunnable();
121 
122     private RectF mDeleteRegion;
123     private DropTarget mLastDropTarget;
124 
125     private InputMethodManager mInputMethodManager;
126 
127     /**
128      * Interface to receive notifications when a drag starts or stops
129      */
130     interface DragListener {
131 
132         /**
133          * A drag has begun
134          *
135          * @param source An object representing where the drag originated
136          * @param info The data associated with the object that is being dragged
137          * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE}
138          *        or {@link DragController#DRAG_ACTION_COPY}
139          */
onDragStart(DragSource source, Object info, int dragAction)140         void onDragStart(DragSource source, Object info, int dragAction);
141 
142         /**
143          * The drag has eneded
144          */
onDragEnd()145         void onDragEnd();
146     }
147 
148     /**
149      * Used to create a new DragLayer from XML.
150      *
151      * @param context The application's context.
152      */
DragController(Context context)153     public DragController(Context context) {
154         mContext = context;
155         mHandler = new Handler();
156         mScrollZone = context.getResources().getDimensionPixelSize(R.dimen.scroll_zone);
157     }
158 
159     /**
160      * Starts a drag.
161      *
162      * @param v The view that is being dragged
163      * @param source An object representing where the drag originated
164      * @param dragInfo The data associated with the object that is being dragged
165      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
166      *        {@link #DRAG_ACTION_COPY}
167      */
startDrag(View v, DragSource source, Object dragInfo, int dragAction)168     public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) {
169         mOriginator = v;
170 
171         Bitmap b = getViewBitmap(v);
172 
173         if (b == null) {
174             // out of memory?
175             return;
176         }
177 
178         int[] loc = mCoordinatesTemp;
179         v.getLocationOnScreen(loc);
180         int screenX = loc[0];
181         int screenY = loc[1];
182 
183         startDrag(b, screenX, screenY, 0, 0, b.getWidth(), b.getHeight(),
184                 source, dragInfo, dragAction);
185 
186         b.recycle();
187 
188         if (dragAction == DRAG_ACTION_MOVE) {
189             v.setVisibility(View.GONE);
190         }
191     }
192 
193     /**
194      * Starts a drag.
195      *
196      * @param b The bitmap to display as the drag image.  It will be re-scaled to the
197      *          enlarged size.
198      * @param screenX The x position on screen of the left-top of the bitmap.
199      * @param screenY The y position on screen of the left-top of the bitmap.
200      * @param textureLeft The left edge of the region inside b to use.
201      * @param textureTop The top edge of the region inside b to use.
202      * @param textureWidth The width of the region inside b to use.
203      * @param textureHeight The height of the region inside b to use.
204      * @param source An object representing where the drag originated
205      * @param dragInfo The data associated with the object that is being dragged
206      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
207      *        {@link #DRAG_ACTION_COPY}
208      */
startDrag(Bitmap b, int screenX, int screenY, int textureLeft, int textureTop, int textureWidth, int textureHeight, DragSource source, Object dragInfo, int dragAction)209     public void startDrag(Bitmap b, int screenX, int screenY,
210             int textureLeft, int textureTop, int textureWidth, int textureHeight,
211             DragSource source, Object dragInfo, int dragAction) {
212         if (PROFILE_DRAWING_DURING_DRAG) {
213             android.os.Debug.startMethodTracing("Launcher");
214         }
215 
216         // Hide soft keyboard, if visible
217         if (mInputMethodManager == null) {
218             mInputMethodManager = (InputMethodManager)
219                     mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
220         }
221         mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
222 
223         if (mListener != null) {
224             mListener.onDragStart(source, dragInfo, dragAction);
225         }
226 
227         int registrationX = ((int)mMotionDownX) - screenX;
228         int registrationY = ((int)mMotionDownY) - screenY;
229 
230         mTouchOffsetX = mMotionDownX - screenX;
231         mTouchOffsetY = mMotionDownY - screenY;
232 
233         mDragging = true;
234         mDragSource = source;
235         mDragInfo = dragInfo;
236 
237         mVibrator.vibrate(VIBRATE_DURATION);
238 
239         DragView dragView = mDragView = new DragView(mContext, b, registrationX, registrationY,
240                 textureLeft, textureTop, textureWidth, textureHeight);
241         dragView.show(mWindowToken, (int)mMotionDownX, (int)mMotionDownY);
242     }
243 
244     /**
245      * Draw the view into a bitmap.
246      */
getViewBitmap(View v)247     private Bitmap getViewBitmap(View v) {
248         v.clearFocus();
249         v.setPressed(false);
250 
251         boolean willNotCache = v.willNotCacheDrawing();
252         v.setWillNotCacheDrawing(false);
253 
254         // Reset the drawing cache background color to fully transparent
255         // for the duration of this operation
256         int color = v.getDrawingCacheBackgroundColor();
257         v.setDrawingCacheBackgroundColor(0);
258 
259         if (color != 0) {
260             v.destroyDrawingCache();
261         }
262         v.buildDrawingCache();
263         Bitmap cacheBitmap = v.getDrawingCache();
264         if (cacheBitmap == null) {
265             Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException());
266             return null;
267         }
268 
269         Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
270 
271         // Restore the view
272         v.destroyDrawingCache();
273         v.setWillNotCacheDrawing(willNotCache);
274         v.setDrawingCacheBackgroundColor(color);
275 
276         return bitmap;
277     }
278 
279     /**
280      * Call this from a drag source view like this:
281      *
282      * <pre>
283      *  @Override
284      *  public boolean dispatchKeyEvent(KeyEvent event) {
285      *      return mDragController.dispatchKeyEvent(this, event)
286      *              || super.dispatchKeyEvent(event);
287      * </pre>
288      */
289     @SuppressWarnings({"UnusedDeclaration"})
dispatchKeyEvent(KeyEvent event)290     public boolean dispatchKeyEvent(KeyEvent event) {
291         return mDragging;
292     }
293 
294     /**
295      * Stop dragging without dropping.
296      */
cancelDrag()297     public void cancelDrag() {
298         endDrag();
299     }
300 
endDrag()301     private void endDrag() {
302         if (mDragging) {
303             mDragging = false;
304             if (mOriginator != null) {
305                 mOriginator.setVisibility(View.VISIBLE);
306             }
307             if (mListener != null) {
308                 mListener.onDragEnd();
309             }
310             if (mDragView != null) {
311                 mDragView.remove();
312                 mDragView = null;
313             }
314         }
315     }
316 
317     /**
318      * Call this from a drag source view.
319      */
onInterceptTouchEvent(MotionEvent ev)320     public boolean onInterceptTouchEvent(MotionEvent ev) {
321         if (false) {
322             Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging="
323                     + mDragging);
324         }
325         final int action = ev.getAction();
326 
327         if (action == MotionEvent.ACTION_DOWN) {
328             recordScreenSize();
329         }
330 
331         final int screenX = clamp((int)ev.getRawX(), 0, mDisplayMetrics.widthPixels);
332         final int screenY = clamp((int)ev.getRawY(), 0, mDisplayMetrics.heightPixels);
333 
334         switch (action) {
335             case MotionEvent.ACTION_MOVE:
336                 break;
337 
338             case MotionEvent.ACTION_DOWN:
339                 // Remember location of down touch
340                 mMotionDownX = screenX;
341                 mMotionDownY = screenY;
342                 mLastDropTarget = null;
343                 break;
344 
345             case MotionEvent.ACTION_CANCEL:
346             case MotionEvent.ACTION_UP:
347                 if (mDragging) {
348                     drop(screenX, screenY);
349                 }
350                 endDrag();
351                 break;
352         }
353 
354         return mDragging;
355     }
356 
357     /**
358      * Sets the view that should handle move events.
359      */
setMoveTarget(View view)360     void setMoveTarget(View view) {
361         mMoveTarget = view;
362     }
363 
dispatchUnhandledMove(View focused, int direction)364     public boolean dispatchUnhandledMove(View focused, int direction) {
365         return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
366     }
367 
368     /**
369      * Call this from a drag source view.
370      */
onTouchEvent(MotionEvent ev)371     public boolean onTouchEvent(MotionEvent ev) {
372         View scrollView = mScrollView;
373 
374         if (!mDragging) {
375             return false;
376         }
377 
378         final int action = ev.getAction();
379         final int screenX = clamp((int)ev.getRawX(), 0, mDisplayMetrics.widthPixels);
380         final int screenY = clamp((int)ev.getRawY(), 0, mDisplayMetrics.heightPixels);
381 
382         switch (action) {
383         case MotionEvent.ACTION_DOWN:
384             // Remember where the motion event started
385             mMotionDownX = screenX;
386             mMotionDownY = screenY;
387 
388             if ((screenX < mScrollZone) || (screenX > scrollView.getWidth() - mScrollZone)) {
389                 mScrollState = SCROLL_WAITING_IN_ZONE;
390                 mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
391             } else {
392                 mScrollState = SCROLL_OUTSIDE_ZONE;
393             }
394 
395             break;
396         case MotionEvent.ACTION_MOVE:
397             // Update the drag view.  Don't use the clamped pos here so the dragging looks
398             // like it goes off screen a little, intead of bumping up against the edge.
399             mDragView.move((int)ev.getRawX(), (int)ev.getRawY());
400 
401             // Drop on someone?
402             final int[] coordinates = mCoordinatesTemp;
403             DropTarget dropTarget = findDropTarget(screenX, screenY, coordinates);
404             if (dropTarget != null) {
405                 if (mLastDropTarget == dropTarget) {
406                     dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1],
407                         (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
408                 } else {
409                     if (mLastDropTarget != null) {
410                         mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
411                             (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
412                     }
413                     dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1],
414                         (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
415                 }
416             } else {
417                 if (mLastDropTarget != null) {
418                     mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
419                         (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
420                 }
421             }
422             mLastDropTarget = dropTarget;
423 
424             // Scroll, maybe, but not if we're in the delete region.
425             boolean inDeleteRegion = false;
426             if (mDeleteRegion != null) {
427                 inDeleteRegion = mDeleteRegion.contains(screenX, screenY);
428             }
429             //Log.d(Launcher.TAG, "inDeleteRegion=" + inDeleteRegion + " screenX=" + screenX
430             //        + " mScrollZone=" + mScrollZone);
431             if (!inDeleteRegion && screenX < mScrollZone) {
432                 if (mScrollState == SCROLL_OUTSIDE_ZONE) {
433                     mScrollState = SCROLL_WAITING_IN_ZONE;
434                     mScrollRunnable.setDirection(SCROLL_LEFT);
435                     mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
436                 }
437             } else if (!inDeleteRegion && screenX > scrollView.getWidth() - mScrollZone) {
438                 if (mScrollState == SCROLL_OUTSIDE_ZONE) {
439                     mScrollState = SCROLL_WAITING_IN_ZONE;
440                     mScrollRunnable.setDirection(SCROLL_RIGHT);
441                     mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
442                 }
443             } else {
444                 if (mScrollState == SCROLL_WAITING_IN_ZONE) {
445                     mScrollState = SCROLL_OUTSIDE_ZONE;
446                     mScrollRunnable.setDirection(SCROLL_RIGHT);
447                     mHandler.removeCallbacks(mScrollRunnable);
448                 }
449             }
450 
451             break;
452         case MotionEvent.ACTION_UP:
453             mHandler.removeCallbacks(mScrollRunnable);
454             if (mDragging) {
455                 drop(screenX, screenY);
456             }
457             endDrag();
458 
459             break;
460         case MotionEvent.ACTION_CANCEL:
461             cancelDrag();
462         }
463 
464         return true;
465     }
466 
drop(float x, float y)467     private boolean drop(float x, float y) {
468         final int[] coordinates = mCoordinatesTemp;
469         DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
470 
471         if (dropTarget != null) {
472             dropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
473                     (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
474             if (dropTarget.acceptDrop(mDragSource, coordinates[0], coordinates[1],
475                     (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo)) {
476                 dropTarget.onDrop(mDragSource, coordinates[0], coordinates[1],
477                         (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
478                 mDragSource.onDropCompleted((View) dropTarget, true);
479                 return true;
480             } else {
481                 mDragSource.onDropCompleted((View) dropTarget, false);
482                 return true;
483             }
484         }
485         return false;
486     }
487 
findDropTarget(int x, int y, int[] dropCoordinates)488     private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
489         final Rect r = mRectTemp;
490 
491         final ArrayList<DropTarget> dropTargets = mDropTargets;
492         final int count = dropTargets.size();
493         for (int i=count-1; i>=0; i--) {
494             final DropTarget target = dropTargets.get(i);
495             target.getHitRect(r);
496             target.getLocationOnScreen(dropCoordinates);
497             r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop());
498             if (r.contains(x, y)) {
499                 dropCoordinates[0] = x - dropCoordinates[0];
500                 dropCoordinates[1] = y - dropCoordinates[1];
501                 return target;
502             }
503         }
504         return null;
505     }
506 
507     /**
508      * Get the screen size so we can clamp events to the screen size so even if
509      * you drag off the edge of the screen, we find something.
510      */
recordScreenSize()511     private void recordScreenSize() {
512         ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
513                 .getDefaultDisplay().getMetrics(mDisplayMetrics);
514     }
515 
516     /**
517      * Clamp val to be &gt;= min and &lt; max.
518      */
clamp(int val, int min, int max)519     private static int clamp(int val, int min, int max) {
520         if (val < min) {
521             return min;
522         } else if (val >= max) {
523             return max - 1;
524         } else {
525             return val;
526         }
527     }
528 
setDragScoller(DragScroller scroller)529     public void setDragScoller(DragScroller scroller) {
530         mDragScroller = scroller;
531     }
532 
setWindowToken(IBinder token)533     public void setWindowToken(IBinder token) {
534         mWindowToken = token;
535     }
536 
537     /**
538      * Sets the drag listner which will be notified when a drag starts or ends.
539      */
setDragListener(DragListener l)540     public void setDragListener(DragListener l) {
541         mListener = l;
542     }
543 
544     /**
545      * Remove a previously installed drag listener.
546      */
removeDragListener(DragListener l)547     public void removeDragListener(DragListener l) {
548         mListener = null;
549     }
550 
551     /**
552      * Add a DropTarget to the list of potential places to receive drop events.
553      */
addDropTarget(DropTarget target)554     public void addDropTarget(DropTarget target) {
555         mDropTargets.add(target);
556     }
557 
558     /**
559      * Don't send drop events to <em>target</em> any more.
560      */
removeDropTarget(DropTarget target)561     public void removeDropTarget(DropTarget target) {
562         mDropTargets.remove(target);
563     }
564 
565     /**
566      * Set which view scrolls for touch events near the edge of the screen.
567      */
setScrollView(View v)568     public void setScrollView(View v) {
569         mScrollView = v;
570     }
571 
572     /**
573      * Specifies the delete region.  We won't scroll on touch events over the delete region.
574      *
575      * @param region The rectangle in screen coordinates of the delete region.
576      */
setDeleteRegion(RectF region)577     void setDeleteRegion(RectF region) {
578         mDeleteRegion = region;
579     }
580 
581     private class ScrollRunnable implements Runnable {
582         private int mDirection;
583 
ScrollRunnable()584         ScrollRunnable() {
585         }
586 
run()587         public void run() {
588             if (mDragScroller != null) {
589                 if (mDirection == SCROLL_LEFT) {
590                     mDragScroller.scrollLeft();
591                 } else {
592                     mDragScroller.scrollRight();
593                 }
594                 mScrollState = SCROLL_OUTSIDE_ZONE;
595             }
596         }
597 
setDirection(int direction)598         void setDirection(int direction) {
599             mDirection = direction;
600         }
601     }
602 }
603