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.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.TimeInterpolator;
22 import android.animation.ValueAnimator;
23 import android.animation.ValueAnimator.AnimatorUpdateListener;
24 import android.content.Context;
25 import android.content.res.Resources;
26 import android.graphics.Canvas;
27 import android.graphics.Rect;
28 import android.graphics.drawable.Drawable;
29 import android.util.AttributeSet;
30 import android.view.KeyEvent;
31 import android.view.MotionEvent;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.view.ViewParent;
35 import android.view.accessibility.AccessibilityEvent;
36 import android.view.accessibility.AccessibilityManager;
37 import android.view.accessibility.AccessibilityNodeInfo;
38 import android.view.animation.DecelerateInterpolator;
39 import android.view.animation.Interpolator;
40 import android.widget.FrameLayout;
41 import android.widget.TextView;
42 
43 import com.android.launcher.R;
44 
45 import java.util.ArrayList;
46 
47 /**
48  * A ViewGroup that coordinates dragging across its descendants
49  */
50 public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChangeListener {
51     private DragController mDragController;
52     private int[] mTmpXY = new int[2];
53 
54     private int mXDown, mYDown;
55     private Launcher mLauncher;
56 
57     // Variables relating to resizing widgets
58     private final ArrayList<AppWidgetResizeFrame> mResizeFrames =
59             new ArrayList<AppWidgetResizeFrame>();
60     private AppWidgetResizeFrame mCurrentResizeFrame;
61 
62     // Variables relating to animation of views after drop
63     private ValueAnimator mDropAnim = null;
64     private ValueAnimator mFadeOutAnim = null;
65     private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
66     private DragView mDropView = null;
67     private int mAnchorViewInitialScrollX = 0;
68     private View mAnchorView = null;
69 
70     private boolean mHoverPointClosesFolder = false;
71     private Rect mHitRect = new Rect();
72     private int mWorkspaceIndex = -1;
73     private int mQsbIndex = -1;
74     public static final int ANIMATION_END_DISAPPEAR = 0;
75     public static final int ANIMATION_END_FADE_OUT = 1;
76     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
77 
78     /**
79      * Used to create a new DragLayer from XML.
80      *
81      * @param context The application's context.
82      * @param attrs The attributes set containing the Workspace's customization values.
83      */
DragLayer(Context context, AttributeSet attrs)84     public DragLayer(Context context, AttributeSet attrs) {
85         super(context, attrs);
86 
87         // Disable multitouch across the workspace/all apps/customize tray
88         setMotionEventSplittingEnabled(false);
89         setChildrenDrawingOrderEnabled(true);
90         setOnHierarchyChangeListener(this);
91 
92         mLeftHoverDrawable = getResources().getDrawable(R.drawable.page_hover_left_holo);
93         mRightHoverDrawable = getResources().getDrawable(R.drawable.page_hover_right_holo);
94     }
95 
setup(Launcher launcher, DragController controller)96     public void setup(Launcher launcher, DragController controller) {
97         mLauncher = launcher;
98         mDragController = controller;
99     }
100 
101     @Override
dispatchKeyEvent(KeyEvent event)102     public boolean dispatchKeyEvent(KeyEvent event) {
103         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
104     }
105 
isEventOverFolderTextRegion(Folder folder, MotionEvent ev)106     private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
107         getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
108         if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
109             return true;
110         }
111         return false;
112     }
113 
isEventOverFolder(Folder folder, MotionEvent ev)114     private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
115         getDescendantRectRelativeToSelf(folder, mHitRect);
116         if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
117             return true;
118         }
119         return false;
120     }
121 
handleTouchDown(MotionEvent ev, boolean intercept)122     private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
123         Rect hitRect = new Rect();
124         int x = (int) ev.getX();
125         int y = (int) ev.getY();
126 
127         for (AppWidgetResizeFrame child: mResizeFrames) {
128             child.getHitRect(hitRect);
129             if (hitRect.contains(x, y)) {
130                 if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
131                     mCurrentResizeFrame = child;
132                     mXDown = x;
133                     mYDown = y;
134                     requestDisallowInterceptTouchEvent(true);
135                     return true;
136                 }
137             }
138         }
139 
140         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
141         if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) {
142             if (currentFolder.isEditingName()) {
143                 if (!isEventOverFolderTextRegion(currentFolder, ev)) {
144                     currentFolder.dismissEditingName();
145                     return true;
146                 }
147             }
148 
149             getDescendantRectRelativeToSelf(currentFolder, hitRect);
150             if (!isEventOverFolder(currentFolder, ev)) {
151                 mLauncher.closeFolder();
152                 return true;
153             }
154         }
155         return false;
156     }
157 
158     @Override
onInterceptTouchEvent(MotionEvent ev)159     public boolean onInterceptTouchEvent(MotionEvent ev) {
160         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
161             if (handleTouchDown(ev, true)) {
162                 return true;
163             }
164         }
165         clearAllResizeFrames();
166         return mDragController.onInterceptTouchEvent(ev);
167     }
168 
169     @Override
onInterceptHoverEvent(MotionEvent ev)170     public boolean onInterceptHoverEvent(MotionEvent ev) {
171         if (mLauncher == null || mLauncher.getWorkspace() == null) {
172             return false;
173         }
174         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
175         if (currentFolder == null) {
176             return false;
177         } else {
178                 AccessibilityManager accessibilityManager = (AccessibilityManager)
179                         getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
180             if (accessibilityManager.isTouchExplorationEnabled()) {
181                 final int action = ev.getAction();
182                 boolean isOverFolder;
183                 switch (action) {
184                     case MotionEvent.ACTION_HOVER_ENTER:
185                         isOverFolder = isEventOverFolder(currentFolder, ev);
186                         if (!isOverFolder) {
187                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
188                             mHoverPointClosesFolder = true;
189                             return true;
190                         } else if (isOverFolder) {
191                             mHoverPointClosesFolder = false;
192                         } else {
193                             return true;
194                         }
195                     case MotionEvent.ACTION_HOVER_MOVE:
196                         isOverFolder = isEventOverFolder(currentFolder, ev);
197                         if (!isOverFolder && !mHoverPointClosesFolder) {
198                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
199                             mHoverPointClosesFolder = true;
200                             return true;
201                         } else if (isOverFolder) {
202                             mHoverPointClosesFolder = false;
203                         } else {
204                             return true;
205                         }
206                 }
207             }
208         }
209         return false;
210     }
211 
sendTapOutsideFolderAccessibilityEvent(boolean isEditingName)212     private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
213         AccessibilityManager accessibilityManager = (AccessibilityManager)
214                 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
215         if (accessibilityManager.isEnabled()) {
216             int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
217             AccessibilityEvent event = AccessibilityEvent.obtain(
218                     AccessibilityEvent.TYPE_VIEW_FOCUSED);
219             onInitializeAccessibilityEvent(event);
220             event.getText().add(getContext().getString(stringId));
221             accessibilityManager.sendAccessibilityEvent(event);
222         }
223     }
224 
225     @Override
onRequestSendAccessibilityEvent(View child, AccessibilityEvent event)226     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
227         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
228         if (currentFolder != null) {
229             if (child == currentFolder) {
230                 return super.onRequestSendAccessibilityEvent(child, event);
231             }
232             // Skip propagating onRequestSendAccessibilityEvent all for other children
233             // when a folder is open
234             return false;
235         }
236         return super.onRequestSendAccessibilityEvent(child, event);
237     }
238 
239     @Override
addChildrenForAccessibility(ArrayList<View> childrenForAccessibility)240     public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
241         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
242         if (currentFolder != null) {
243             // Only add the folder as a child for accessibility when it is open
244             childrenForAccessibility.add(currentFolder);
245         } else {
246             super.addChildrenForAccessibility(childrenForAccessibility);
247         }
248     }
249 
250     @Override
onHoverEvent(MotionEvent ev)251     public boolean onHoverEvent(MotionEvent ev) {
252         // If we've received this, we've already done the necessary handling
253         // in onInterceptHoverEvent. Return true to consume the event.
254         return false;
255     }
256 
257     @Override
onTouchEvent(MotionEvent ev)258     public boolean onTouchEvent(MotionEvent ev) {
259         boolean handled = false;
260         int action = ev.getAction();
261 
262         int x = (int) ev.getX();
263         int y = (int) ev.getY();
264 
265         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
266             if (ev.getAction() == MotionEvent.ACTION_DOWN) {
267                 if (handleTouchDown(ev, false)) {
268                     return true;
269                 }
270             }
271         }
272 
273         if (mCurrentResizeFrame != null) {
274             handled = true;
275             switch (action) {
276                 case MotionEvent.ACTION_MOVE:
277                     mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
278                     break;
279                 case MotionEvent.ACTION_CANCEL:
280                 case MotionEvent.ACTION_UP:
281                     mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
282                     mCurrentResizeFrame.onTouchUp();
283                     mCurrentResizeFrame = null;
284             }
285         }
286         if (handled) return true;
287         return mDragController.onTouchEvent(ev);
288     }
289 
290     /**
291      * Determine the rect of the descendant in this DragLayer's coordinates
292      *
293      * @param descendant The descendant whose coordinates we want to find.
294      * @param r The rect into which to place the results.
295      * @return The factor by which this descendant is scaled relative to this DragLayer.
296      */
getDescendantRectRelativeToSelf(View descendant, Rect r)297     public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
298         mTmpXY[0] = 0;
299         mTmpXY[1] = 0;
300         float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
301         r.set(mTmpXY[0], mTmpXY[1],
302                 mTmpXY[0] + descendant.getWidth(), mTmpXY[1] + descendant.getHeight());
303         return scale;
304     }
305 
getLocationInDragLayer(View child, int[] loc)306     public float getLocationInDragLayer(View child, int[] loc) {
307         loc[0] = 0;
308         loc[1] = 0;
309         return getDescendantCoordRelativeToSelf(child, loc);
310     }
311 
312     /**
313      * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
314      * coordinates.
315      *
316      * @param descendant The descendant to which the passed coordinate is relative.
317      * @param coord The coordinate that we want mapped.
318      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
319      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
320      *         assumption fails, we will need to return a pair of scale factors.
321      */
getDescendantCoordRelativeToSelf(View descendant, int[] coord)322     public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
323         float scale = 1.0f;
324         float[] pt = {coord[0], coord[1]};
325         descendant.getMatrix().mapPoints(pt);
326         scale *= descendant.getScaleX();
327         pt[0] += descendant.getLeft();
328         pt[1] += descendant.getTop();
329         ViewParent viewParent = descendant.getParent();
330         while (viewParent instanceof View && viewParent != this) {
331             final View view = (View)viewParent;
332             view.getMatrix().mapPoints(pt);
333             scale *= view.getScaleX();
334             pt[0] += view.getLeft() - view.getScrollX();
335             pt[1] += view.getTop() - view.getScrollY();
336             viewParent = view.getParent();
337         }
338         coord[0] = (int) Math.round(pt[0]);
339         coord[1] = (int) Math.round(pt[1]);
340         return scale;
341     }
342 
getViewRectRelativeToSelf(View v, Rect r)343     public void getViewRectRelativeToSelf(View v, Rect r) {
344         int[] loc = new int[2];
345         getLocationInWindow(loc);
346         int x = loc[0];
347         int y = loc[1];
348 
349         v.getLocationInWindow(loc);
350         int vX = loc[0];
351         int vY = loc[1];
352 
353         int left = vX - x;
354         int top = vY - y;
355         r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
356     }
357 
358     @Override
dispatchUnhandledMove(View focused, int direction)359     public boolean dispatchUnhandledMove(View focused, int direction) {
360         return mDragController.dispatchUnhandledMove(focused, direction);
361     }
362 
363     public static class LayoutParams extends FrameLayout.LayoutParams {
364         public int x, y;
365         public boolean customPosition = false;
366 
367         /**
368          * {@inheritDoc}
369          */
LayoutParams(int width, int height)370         public LayoutParams(int width, int height) {
371             super(width, height);
372         }
373 
setWidth(int width)374         public void setWidth(int width) {
375             this.width = width;
376         }
377 
getWidth()378         public int getWidth() {
379             return width;
380         }
381 
setHeight(int height)382         public void setHeight(int height) {
383             this.height = height;
384         }
385 
getHeight()386         public int getHeight() {
387             return height;
388         }
389 
setX(int x)390         public void setX(int x) {
391             this.x = x;
392         }
393 
getX()394         public int getX() {
395             return x;
396         }
397 
setY(int y)398         public void setY(int y) {
399             this.y = y;
400         }
401 
getY()402         public int getY() {
403             return y;
404         }
405     }
406 
onLayout(boolean changed, int l, int t, int r, int b)407     protected void onLayout(boolean changed, int l, int t, int r, int b) {
408         super.onLayout(changed, l, t, r, b);
409         int count = getChildCount();
410         for (int i = 0; i < count; i++) {
411             View child = getChildAt(i);
412             final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
413             if (flp instanceof LayoutParams) {
414                 final LayoutParams lp = (LayoutParams) flp;
415                 if (lp.customPosition) {
416                     child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
417                 }
418             }
419         }
420     }
421 
clearAllResizeFrames()422     public void clearAllResizeFrames() {
423         if (mResizeFrames.size() > 0) {
424             for (AppWidgetResizeFrame frame: mResizeFrames) {
425                 frame.commitResize();
426                 removeView(frame);
427             }
428             mResizeFrames.clear();
429         }
430     }
431 
hasResizeFrames()432     public boolean hasResizeFrames() {
433         return mResizeFrames.size() > 0;
434     }
435 
isWidgetBeingResized()436     public boolean isWidgetBeingResized() {
437         return mCurrentResizeFrame != null;
438     }
439 
addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, CellLayout cellLayout)440     public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
441             CellLayout cellLayout) {
442         AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
443                 widget, cellLayout, this);
444 
445         LayoutParams lp = new LayoutParams(-1, -1);
446         lp.customPosition = true;
447 
448         addView(resizeFrame, lp);
449         mResizeFrames.add(resizeFrame);
450 
451         resizeFrame.snapToWidget(false);
452     }
453 
animateViewIntoPosition(DragView dragView, final View child)454     public void animateViewIntoPosition(DragView dragView, final View child) {
455         animateViewIntoPosition(dragView, child, null);
456     }
457 
animateViewIntoPosition(DragView dragView, final int[] pos, float alpha, float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable, int duration)458     public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
459             float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
460             int duration) {
461         Rect r = new Rect();
462         getViewRectRelativeToSelf(dragView, r);
463         final int fromX = r.left;
464         final int fromY = r.top;
465 
466         animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
467                 onFinishRunnable, animationEndStyle, duration, null);
468     }
469 
animateViewIntoPosition(DragView dragView, final View child, final Runnable onFinishAnimationRunnable)470     public void animateViewIntoPosition(DragView dragView, final View child,
471             final Runnable onFinishAnimationRunnable) {
472         animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, null);
473     }
474 
animateViewIntoPosition(DragView dragView, final View child, int duration, final Runnable onFinishAnimationRunnable, View anchorView)475     public void animateViewIntoPosition(DragView dragView, final View child, int duration,
476             final Runnable onFinishAnimationRunnable, View anchorView) {
477         ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
478         CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
479         parentChildren.measureChild(child);
480 
481         Rect r = new Rect();
482         getViewRectRelativeToSelf(dragView, r);
483 
484         int coord[] = new int[2];
485         float childScale = child.getScaleX();
486         coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2);
487         coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2);
488 
489         // Since the child hasn't necessarily been laid out, we force the lp to be updated with
490         // the correct coordinates (above) and use these to determine the final location
491         float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
492         // We need to account for the scale of the child itself, as the above only accounts for
493         // for the scale in parents.
494         scale *= childScale;
495         int toX = coord[0];
496         int toY = coord[1];
497         if (child instanceof TextView) {
498             TextView tv = (TextView) child;
499 
500             // The child may be scaled (always about the center of the view) so to account for it,
501             // we have to offset the position by the scaled size.  Once we do that, we can center
502             // the drag view about the scaled child view.
503             toY += Math.round(scale * tv.getPaddingTop());
504             toY -= dragView.getMeasuredHeight() * (1 - scale) / 2;
505             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
506         } else if (child instanceof FolderIcon) {
507             // Account for holographic blur padding on the drag view
508             toY -= scale * Workspace.DRAG_BITMAP_PADDING / 2;
509             toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
510             // Center in the x coordinate about the target's drawable
511             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
512         } else {
513             toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
514             toX -= (Math.round(scale * (dragView.getMeasuredWidth()
515                     - child.getMeasuredWidth()))) / 2;
516         }
517 
518         final int fromX = r.left;
519         final int fromY = r.top;
520         child.setVisibility(INVISIBLE);
521         Runnable onCompleteRunnable = new Runnable() {
522             public void run() {
523                 child.setVisibility(VISIBLE);
524                 if (onFinishAnimationRunnable != null) {
525                     onFinishAnimationRunnable.run();
526                 }
527             }
528         };
529         animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale,
530                 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
531     }
532 
animateViewIntoPosition(final DragView view, final int fromX, final int fromY, final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY, float finalScaleX, float finalScaleY, Runnable onCompleteRunnable, int animationEndStyle, int duration, View anchorView)533     public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
534             final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
535             float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
536             int animationEndStyle, int duration, View anchorView) {
537         Rect from = new Rect(fromX, fromY, fromX +
538                 view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
539         Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
540         animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
541                 null, null, onCompleteRunnable, animationEndStyle, anchorView);
542     }
543 
544     /**
545      * This method animates a view at the end of a drag and drop animation.
546      *
547      * @param view The view to be animated. This view is drawn directly into DragLayer, and so
548      *        doesn't need to be a child of DragLayer.
549      * @param from The initial location of the view. Only the left and top parameters are used.
550      * @param to The final location of the view. Only the left and top parameters are used. This
551      *        location doesn't account for scaling, and so should be centered about the desired
552      *        final location (including scaling).
553      * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
554      * @param finalScale The final scale of the view. The view is scaled about its center.
555      * @param duration The duration of the animation.
556      * @param motionInterpolator The interpolator to use for the location of the view.
557      * @param alphaInterpolator The interpolator to use for the alpha of the view.
558      * @param onCompleteRunnable Optional runnable to run on animation completion.
559      * @param fadeOut Whether or not to fade out the view once the animation completes. If true,
560      *        the runnable will execute after the view is faded out.
561      * @param anchorView If not null, this represents the view which the animated view stays
562      *        anchored to in case scrolling is currently taking place. Note: currently this is
563      *        only used for the X dimension for the case of the workspace.
564      */
animateView(final DragView view, final Rect from, final Rect to, final float finalAlpha, final float initScaleX, final float initScaleY, final float finalScaleX, final float finalScaleY, int duration, final Interpolator motionInterpolator, final Interpolator alphaInterpolator, final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView)565     public void animateView(final DragView view, final Rect from, final Rect to,
566             final float finalAlpha, final float initScaleX, final float initScaleY,
567             final float finalScaleX, final float finalScaleY, int duration,
568             final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
569             final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
570 
571         // Calculate the duration of the animation based on the object's distance
572         final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) +
573                 Math.pow(to.top - from.top, 2));
574         final Resources res = getResources();
575         final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
576 
577         // If duration < 0, this is a cue to compute the duration based on the distance
578         if (duration < 0) {
579             duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
580             if (dist < maxDist) {
581                 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
582             }
583             duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
584         }
585 
586         // Fall back to cubic ease out interpolator for the animation if none is specified
587         TimeInterpolator interpolator = null;
588         if (alphaInterpolator == null || motionInterpolator == null) {
589             interpolator = mCubicEaseOutInterpolator;
590         }
591 
592         // Animate the view
593         final float initAlpha = view.getAlpha();
594         final float dropViewScale = view.getScaleX();
595         AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
596             @Override
597             public void onAnimationUpdate(ValueAnimator animation) {
598                 final float percent = (Float) animation.getAnimatedValue();
599                 final int width = view.getMeasuredWidth();
600                 final int height = view.getMeasuredHeight();
601 
602                 float alphaPercent = alphaInterpolator == null ? percent :
603                         alphaInterpolator.getInterpolation(percent);
604                 float motionPercent = motionInterpolator == null ? percent :
605                         motionInterpolator.getInterpolation(percent);
606 
607                 float initialScaleX = initScaleX * dropViewScale;
608                 float initialScaleY = initScaleY * dropViewScale;
609                 float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
610                 float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
611                 float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
612 
613                 float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
614                 float fromTop = from.top + (initialScaleY - 1f) * height / 2;
615 
616                 int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
617                 int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
618 
619                 int xPos = x - mDropView.getScrollX() + (mAnchorView != null
620                         ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0);
621                 int yPos = y - mDropView.getScrollY();
622 
623                 mDropView.setTranslationX(xPos);
624                 mDropView.setTranslationY(yPos);
625                 mDropView.setScaleX(scaleX);
626                 mDropView.setScaleY(scaleY);
627                 mDropView.setAlpha(alpha);
628             }
629         };
630         animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
631                 anchorView);
632     }
633 
animateView(final DragView view, AnimatorUpdateListener updateCb, int duration, TimeInterpolator interpolator, final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView)634     public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
635             TimeInterpolator interpolator, final Runnable onCompleteRunnable,
636             final int animationEndStyle, View anchorView) {
637         // Clean up the previous animations
638         if (mDropAnim != null) mDropAnim.cancel();
639         if (mFadeOutAnim != null) mFadeOutAnim.cancel();
640 
641         // Show the drop view if it was previously hidden
642         mDropView = view;
643         mDropView.cancelAnimation();
644         mDropView.resetLayoutParams();
645 
646         // Set the anchor view if the page is scrolling
647         if (anchorView != null) {
648             mAnchorViewInitialScrollX = anchorView.getScrollX();
649         }
650         mAnchorView = anchorView;
651 
652         // Create and start the animation
653         mDropAnim = new ValueAnimator();
654         mDropAnim.setInterpolator(interpolator);
655         mDropAnim.setDuration(duration);
656         mDropAnim.setFloatValues(0f, 1f);
657         mDropAnim.addUpdateListener(updateCb);
658         mDropAnim.addListener(new AnimatorListenerAdapter() {
659             public void onAnimationEnd(Animator animation) {
660                 if (onCompleteRunnable != null) {
661                     onCompleteRunnable.run();
662                 }
663                 switch (animationEndStyle) {
664                 case ANIMATION_END_DISAPPEAR:
665                     clearAnimatedView();
666                     break;
667                 case ANIMATION_END_FADE_OUT:
668                     fadeOutDragView();
669                     break;
670                 case ANIMATION_END_REMAIN_VISIBLE:
671                     break;
672                 }
673             }
674         });
675         mDropAnim.start();
676     }
677 
clearAnimatedView()678     public void clearAnimatedView() {
679         if (mDropAnim != null) {
680             mDropAnim.cancel();
681         }
682         if (mDropView != null) {
683             mDragController.onDeferredEndDrag(mDropView);
684         }
685         mDropView = null;
686         invalidate();
687     }
688 
getAnimatedView()689     public View getAnimatedView() {
690         return mDropView;
691     }
692 
fadeOutDragView()693     private void fadeOutDragView() {
694         mFadeOutAnim = new ValueAnimator();
695         mFadeOutAnim.setDuration(150);
696         mFadeOutAnim.setFloatValues(0f, 1f);
697         mFadeOutAnim.removeAllUpdateListeners();
698         mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() {
699             public void onAnimationUpdate(ValueAnimator animation) {
700                 final float percent = (Float) animation.getAnimatedValue();
701 
702                 float alpha = 1 - percent;
703                 mDropView.setAlpha(alpha);
704             }
705         });
706         mFadeOutAnim.addListener(new AnimatorListenerAdapter() {
707             public void onAnimationEnd(Animator animation) {
708                 if (mDropView != null) {
709                     mDragController.onDeferredEndDrag(mDropView);
710                 }
711                 mDropView = null;
712                 invalidate();
713             }
714         });
715         mFadeOutAnim.start();
716     }
717 
718     @Override
onChildViewAdded(View parent, View child)719     public void onChildViewAdded(View parent, View child) {
720         updateChildIndices();
721     }
722 
723     @Override
onChildViewRemoved(View parent, View child)724     public void onChildViewRemoved(View parent, View child) {
725         updateChildIndices();
726     }
727 
updateChildIndices()728     private void updateChildIndices() {
729         if (mLauncher != null) {
730             mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace());
731             mQsbIndex = indexOfChild(mLauncher.getSearchBar());
732         }
733     }
734 
735     @Override
getChildDrawingOrder(int childCount, int i)736     protected int getChildDrawingOrder(int childCount, int i) {
737         // TODO: We have turned off this custom drawing order because it now effects touch
738         // dispatch order. We need to sort that issue out and then decide how to go about this.
739         if (true || LauncherApplication.isScreenLandscape(getContext()) ||
740                 mWorkspaceIndex == -1 || mQsbIndex == -1 ||
741                 mLauncher.getWorkspace().isDrawingBackgroundGradient()) {
742             return i;
743         }
744 
745         // This ensures that the workspace is drawn above the hotseat and qsb,
746         // except when the workspace is drawing a background gradient, in which
747         // case we want the workspace to stay behind these elements.
748         if (i == mQsbIndex) {
749             return mWorkspaceIndex;
750         } else if (i == mWorkspaceIndex) {
751             return mQsbIndex;
752         } else {
753             return i;
754         }
755     }
756 
757     private boolean mInScrollArea;
758     private Drawable mLeftHoverDrawable;
759     private Drawable mRightHoverDrawable;
760 
onEnterScrollArea(int direction)761     void onEnterScrollArea(int direction) {
762         mInScrollArea = true;
763         invalidate();
764     }
765 
onExitScrollArea()766     void onExitScrollArea() {
767         mInScrollArea = false;
768         invalidate();
769     }
770 
771     /**
772      * Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api.
773      */
isLayoutDirectionRtl()774     private boolean isLayoutDirectionRtl() {
775         return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
776     }
777 
778     @Override
dispatchDraw(Canvas canvas)779     protected void dispatchDraw(Canvas canvas) {
780         super.dispatchDraw(canvas);
781 
782         if (mInScrollArea && !LauncherApplication.isScreenLarge()) {
783             Workspace workspace = mLauncher.getWorkspace();
784             int width = workspace.getWidth();
785             Rect childRect = new Rect();
786             getDescendantRectRelativeToSelf(workspace.getChildAt(0), childRect);
787 
788             int page = workspace.getNextPage();
789             final boolean isRtl = isLayoutDirectionRtl();
790             CellLayout leftPage = (CellLayout) workspace.getChildAt(isRtl ? page + 1 : page - 1);
791             CellLayout rightPage = (CellLayout) workspace.getChildAt(isRtl ? page - 1 : page + 1);
792 
793             if (leftPage != null && leftPage.getIsDragOverlapping()) {
794                 mLeftHoverDrawable.setBounds(0, childRect.top,
795                         mLeftHoverDrawable.getIntrinsicWidth(), childRect.bottom);
796                 mLeftHoverDrawable.draw(canvas);
797             } else if (rightPage != null && rightPage.getIsDragOverlapping()) {
798                 mRightHoverDrawable.setBounds(width - mRightHoverDrawable.getIntrinsicWidth(),
799                         childRect.top, width, childRect.bottom);
800                 mRightHoverDrawable.draw(canvas);
801             }
802         }
803     }
804 }
805