OSDN Git Service

b04d5b74764c940e5aea0be9a0ef799c9fba61b1
[android-x86/packages-apps-Launcher3.git] / src / com / android / launcher3 / dragndrop / DragLayer.java
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.launcher3.dragndrop;
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.Color;
28 import android.graphics.Rect;
29 import android.graphics.Region;
30 import android.util.AttributeSet;
31 import android.view.KeyEvent;
32 import android.view.LayoutInflater;
33 import android.view.MotionEvent;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.view.accessibility.AccessibilityEvent;
37 import android.view.accessibility.AccessibilityManager;
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.launcher3.AbstractFloatingView;
44 import com.android.launcher3.AppWidgetResizeFrame;
45 import com.android.launcher3.CellLayout;
46 import com.android.launcher3.DropTargetBar;
47 import com.android.launcher3.ExtendedEditText;
48 import com.android.launcher3.InsettableFrameLayout;
49 import com.android.launcher3.Launcher;
50 import com.android.launcher3.LauncherAppWidgetHostView;
51 import com.android.launcher3.PinchToOverviewListener;
52 import com.android.launcher3.R;
53 import com.android.launcher3.ShortcutAndWidgetContainer;
54 import com.android.launcher3.Utilities;
55 import com.android.launcher3.allapps.AllAppsTransitionController;
56 import com.android.launcher3.config.FeatureFlags;
57 import com.android.launcher3.folder.Folder;
58 import com.android.launcher3.folder.FolderIcon;
59 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
60 import com.android.launcher3.logging.LoggerUtils;
61 import com.android.launcher3.userevent.nano.LauncherLogProto;
62 import com.android.launcher3.util.Thunk;
63 import com.android.launcher3.util.TouchController;
64 import com.android.launcher3.widget.WidgetsAndMore;
65
66 import java.util.ArrayList;
67
68 /**
69  * A ViewGroup that coordinates dragging across its descendants
70  */
71 public class DragLayer extends InsettableFrameLayout {
72
73     public static final int ANIMATION_END_DISAPPEAR = 0;
74     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
75
76     // Scrim color without any alpha component.
77     private static final int SCRIM_COLOR = Color.BLACK & 0x00FFFFFF;
78
79     private final int[] mTmpXY = new int[2];
80
81     @Thunk DragController mDragController;
82
83     private Launcher mLauncher;
84
85     // Variables relating to resizing widgets
86     private final boolean mIsRtl;
87     private AppWidgetResizeFrame mCurrentResizeFrame;
88
89     // Variables relating to animation of views after drop
90     private ValueAnimator mDropAnim = null;
91     private final TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
92     @Thunk DragView mDropView = null;
93     @Thunk int mAnchorViewInitialScrollX = 0;
94     @Thunk View mAnchorView = null;
95
96     private boolean mHoverPointClosesFolder = false;
97     private final Rect mHitRect = new Rect();
98     private final Rect mHighlightRect = new Rect();
99
100     private TouchCompleteListener mTouchCompleteListener;
101
102     private int mTopViewIndex;
103     private int mChildCountOnLastUpdate = -1;
104
105     // Darkening scrim
106     private float mBackgroundAlpha = 0;
107
108     // Related to adjacent page hints
109     private final Rect mScrollChildPosition = new Rect();
110     private final ViewGroupFocusHelper mFocusIndicatorHelper;
111
112     // Related to pinch-to-go-to-overview gesture.
113     private PinchToOverviewListener mPinchListener = null;
114
115     // Handles all apps pull up interaction
116     private AllAppsTransitionController mAllAppsController;
117
118     private TouchController mActiveController;
119     /**
120      * Used to create a new DragLayer from XML.
121      *
122      * @param context The application's context.
123      * @param attrs The attributes set containing the Workspace's customization values.
124      */
125     public DragLayer(Context context, AttributeSet attrs) {
126         super(context, attrs);
127
128         // Disable multitouch across the workspace/all apps/customize tray
129         setMotionEventSplittingEnabled(false);
130         setChildrenDrawingOrderEnabled(true);
131
132         mIsRtl = Utilities.isRtl(getResources());
133         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
134     }
135
136     public void setup(Launcher launcher, DragController dragController,
137             AllAppsTransitionController allAppsTransitionController) {
138         mLauncher = launcher;
139         mDragController = dragController;
140         mAllAppsController = allAppsTransitionController;
141
142         boolean isAccessibilityEnabled = ((AccessibilityManager) mLauncher.getSystemService(
143                 Context.ACCESSIBILITY_SERVICE)).isEnabled();
144         onAccessibilityStateChanged(isAccessibilityEnabled);
145     }
146
147     public ViewGroupFocusHelper getFocusIndicatorHelper() {
148         return mFocusIndicatorHelper;
149     }
150
151     @Override
152     public boolean dispatchKeyEvent(KeyEvent event) {
153         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
154     }
155
156     public void onAccessibilityStateChanged(boolean isAccessibilityEnabled) {
157         mPinchListener = FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW || isAccessibilityEnabled
158                 ? null : new PinchToOverviewListener(mLauncher);
159     }
160
161     public boolean isEventOverPageIndicator(MotionEvent ev) {
162         return isEventOverView(mLauncher.getWorkspace().getPageIndicator(), ev);
163     }
164
165     public boolean isEventOverHotseat(MotionEvent ev) {
166         return isEventOverView(mLauncher.getHotseat(), ev);
167     }
168
169     private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
170         return isEventOverView(folder, ev);
171     }
172
173     private boolean isEventOverDropTargetBar(MotionEvent ev) {
174         return isEventOverView(mLauncher.getDropTargetBar(), ev);
175     }
176
177     public boolean isEventOverView(View view, MotionEvent ev) {
178         getDescendantRectRelativeToSelf(view, mHitRect);
179         return mHitRect.contains((int) ev.getX(), (int) ev.getY());
180     }
181
182     private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
183         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
184         if (topView != null && intercept) {
185             ExtendedEditText textView = topView.getActiveTextView();
186             if (textView != null) {
187                 if (!isEventOverView(textView, ev)) {
188                     textView.dispatchBackKey();
189                     return true;
190                 }
191             } else if (!isEventOverView(topView, ev)) {
192                 if (isInAccessibleDrag()) {
193                     // Do not close the container if in drag and drop.
194                     if (!isEventOverDropTargetBar(ev)) {
195                         return true;
196                     }
197                 } else {
198                     mLauncher.getUserEventDispatcher().logActionTapOutside(
199                             LoggerUtils.newContainerTarget(topView.getLogContainerType()));
200                     topView.close(true);
201
202                     // We let touches on the original icon go through so that users can launch
203                     // the app with one tap if they don't find a shortcut they want.
204                     View extendedTouch = topView.getExtendedTouchView();
205                     return extendedTouch == null || !isEventOverView(extendedTouch, ev);
206                 }
207             }
208         }
209         return false;
210     }
211
212     @Override
213     public boolean onInterceptTouchEvent(MotionEvent ev) {
214         int action = ev.getAction();
215
216         if (action == MotionEvent.ACTION_DOWN) {
217             // Cancel discovery bounce animation when a user start interacting on anywhere on
218             // dray layer even if mAllAppsController is NOT the active controller.
219             // TODO: handle other input other than touch
220             mAllAppsController.cancelDiscoveryAnimation();
221             if (handleTouchDown(ev, true)) {
222                 return true;
223             }
224         } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
225             if (mTouchCompleteListener != null) {
226                 mTouchCompleteListener.onTouchComplete();
227             }
228             mTouchCompleteListener = null;
229         }
230         mActiveController = null;
231
232         if (mCurrentResizeFrame != null
233                 && mCurrentResizeFrame.onControllerInterceptTouchEvent(ev)) {
234             mActiveController = mCurrentResizeFrame;
235             return true;
236         } else {
237             clearResizeFrame();
238         }
239
240         if (mDragController.onControllerInterceptTouchEvent(ev)) {
241             mActiveController = mDragController;
242             return true;
243         }
244
245         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onControllerInterceptTouchEvent(ev)) {
246             mActiveController = mAllAppsController;
247             return true;
248         }
249
250         WidgetsAndMore widgetsAndMore = WidgetsAndMore.getOpen(mLauncher);
251         if (widgetsAndMore != null && widgetsAndMore.onControllerInterceptTouchEvent(ev)) {
252             mActiveController = widgetsAndMore;
253             return true;
254         }
255
256         if (mPinchListener != null && mPinchListener.onControllerInterceptTouchEvent(ev)) {
257             // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
258             mActiveController = mPinchListener;
259             return true;
260         }
261         return false;
262     }
263
264     @Override
265     public boolean onInterceptHoverEvent(MotionEvent ev) {
266         if (mLauncher == null || mLauncher.getWorkspace() == null) {
267             return false;
268         }
269         Folder currentFolder = Folder.getOpen(mLauncher);
270         if (currentFolder == null) {
271             return false;
272         } else {
273                 AccessibilityManager accessibilityManager = (AccessibilityManager)
274                         getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
275             if (accessibilityManager.isTouchExplorationEnabled()) {
276                 final int action = ev.getAction();
277                 boolean isOverFolderOrSearchBar;
278                 switch (action) {
279                     case MotionEvent.ACTION_HOVER_ENTER:
280                         isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
281                             (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
282                         if (!isOverFolderOrSearchBar) {
283                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
284                             mHoverPointClosesFolder = true;
285                             return true;
286                         }
287                         mHoverPointClosesFolder = false;
288                         break;
289                     case MotionEvent.ACTION_HOVER_MOVE:
290                         isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
291                             (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
292                         if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) {
293                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
294                             mHoverPointClosesFolder = true;
295                             return true;
296                         } else if (!isOverFolderOrSearchBar) {
297                             return true;
298                         }
299                         mHoverPointClosesFolder = false;
300                 }
301             }
302         }
303         return false;
304     }
305
306     private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
307         int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
308         Utilities.sendCustomAccessibilityEvent(
309                 this, AccessibilityEvent.TYPE_VIEW_FOCUSED, getContext().getString(stringId));
310     }
311
312     private boolean isInAccessibleDrag() {
313         return mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
314     }
315
316     @Override
317     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
318         // Shortcuts can appear above folder
319         View topView = AbstractFloatingView.getTopOpenView(mLauncher);
320         if (topView != null) {
321             if (child == topView) {
322                 return super.onRequestSendAccessibilityEvent(child, event);
323             }
324             if (isInAccessibleDrag() && child instanceof DropTargetBar) {
325                 return super.onRequestSendAccessibilityEvent(child, event);
326             }
327             // Skip propagating onRequestSendAccessibilityEvent for all other children
328             // which are not topView
329             return false;
330         }
331         return super.onRequestSendAccessibilityEvent(child, event);
332     }
333
334     @Override
335     public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
336         View topView = AbstractFloatingView.getTopOpenView(mLauncher);
337         if (topView != null) {
338             // Only add the top view as a child for accessibility when it is open
339             childrenForAccessibility.add(topView);
340
341             if (isInAccessibleDrag()) {
342                 childrenForAccessibility.add(mLauncher.getDropTargetBar());
343             }
344         } else {
345             super.addChildrenForAccessibility(childrenForAccessibility);
346         }
347     }
348
349     @Override
350     public boolean onHoverEvent(MotionEvent ev) {
351         // If we've received this, we've already done the necessary handling
352         // in onInterceptHoverEvent. Return true to consume the event.
353         return false;
354     }
355
356     @Override
357     public boolean onTouchEvent(MotionEvent ev) {
358         int action = ev.getAction();
359
360         if (action == MotionEvent.ACTION_DOWN) {
361             if (handleTouchDown(ev, false)) {
362                 return true;
363             }
364         } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
365             if (mTouchCompleteListener != null) {
366                 mTouchCompleteListener.onTouchComplete();
367             }
368             mTouchCompleteListener = null;
369         }
370
371         if (mActiveController != null) {
372             return mActiveController.onControllerTouchEvent(ev);
373         }
374         return false;
375     }
376
377     /**
378      * Determine the rect of the descendant in this DragLayer's coordinates
379      *
380      * @param descendant The descendant whose coordinates we want to find.
381      * @param r The rect into which to place the results.
382      * @return The factor by which this descendant is scaled relative to this DragLayer.
383      */
384     public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
385         mTmpXY[0] = 0;
386         mTmpXY[1] = 0;
387         float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
388
389         r.set(mTmpXY[0], mTmpXY[1],
390                 (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()),
391                 (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight()));
392         return scale;
393     }
394
395     public float getLocationInDragLayer(View child, int[] loc) {
396         loc[0] = 0;
397         loc[1] = 0;
398         return getDescendantCoordRelativeToSelf(child, loc);
399     }
400
401     public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
402         return getDescendantCoordRelativeToSelf(descendant, coord, false);
403     }
404
405     /**
406      * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
407      * coordinates.
408      *
409      * @param descendant The descendant to which the passed coordinate is relative.
410      * @param coord The coordinate that we want mapped.
411      * @param includeRootScroll Whether or not to account for the scroll of the root descendant:
412      *          sometimes this is relevant as in a child's coordinates within the root descendant.
413      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
414      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
415      *         assumption fails, we will need to return a pair of scale factors.
416      */
417     public float getDescendantCoordRelativeToSelf(View descendant, int[] coord,
418             boolean includeRootScroll) {
419         return Utilities.getDescendantCoordRelativeToAncestor(descendant, this,
420                 coord, includeRootScroll);
421     }
422
423     /**
424      * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
425      */
426     public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
427         Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
428     }
429
430     public void getViewRectRelativeToSelf(View v, Rect r) {
431         int[] loc = new int[2];
432         getLocationInWindow(loc);
433         int x = loc[0];
434         int y = loc[1];
435
436         v.getLocationInWindow(loc);
437         int vX = loc[0];
438         int vY = loc[1];
439
440         int left = vX - x;
441         int top = vY - y;
442         r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
443     }
444
445     @Override
446     public boolean dispatchUnhandledMove(View focused, int direction) {
447         // Consume the unhandled move if a container is open, to avoid switching pages underneath.
448         boolean isContainerOpen = AbstractFloatingView.getTopOpenView(mLauncher) != null;
449         return isContainerOpen || mDragController.dispatchUnhandledMove(focused, direction);
450     }
451
452     @Override
453     public LayoutParams generateLayoutParams(AttributeSet attrs) {
454         return new LayoutParams(getContext(), attrs);
455     }
456
457     @Override
458     protected LayoutParams generateDefaultLayoutParams() {
459         return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
460     }
461
462     // Override to allow type-checking of LayoutParams.
463     @Override
464     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
465         return p instanceof LayoutParams;
466     }
467
468     @Override
469     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
470         return new LayoutParams(p);
471     }
472
473     public static class LayoutParams extends InsettableFrameLayout.LayoutParams {
474         public int x, y;
475         public boolean customPosition = false;
476
477         public LayoutParams(Context c, AttributeSet attrs) {
478             super(c, attrs);
479         }
480
481         public LayoutParams(int width, int height) {
482             super(width, height);
483         }
484
485         public LayoutParams(ViewGroup.LayoutParams lp) {
486             super(lp);
487         }
488
489         public void setWidth(int width) {
490             this.width = width;
491         }
492
493         public int getWidth() {
494             return width;
495         }
496
497         public void setHeight(int height) {
498             this.height = height;
499         }
500
501         public int getHeight() {
502             return height;
503         }
504
505         public void setX(int x) {
506             this.x = x;
507         }
508
509         public int getX() {
510             return x;
511         }
512
513         public void setY(int y) {
514             this.y = y;
515         }
516
517         public int getY() {
518             return y;
519         }
520     }
521
522     protected void onLayout(boolean changed, int l, int t, int r, int b) {
523         super.onLayout(changed, l, t, r, b);
524         int count = getChildCount();
525         for (int i = 0; i < count; i++) {
526             View child = getChildAt(i);
527             final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
528             if (flp instanceof LayoutParams) {
529                 final LayoutParams lp = (LayoutParams) flp;
530                 if (lp.customPosition) {
531                     child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
532                 }
533             }
534         }
535     }
536
537     public void clearResizeFrame() {
538         if (mCurrentResizeFrame != null) {
539             mCurrentResizeFrame.commitResize();
540             removeView(mCurrentResizeFrame);
541             mCurrentResizeFrame = null;
542         }
543     }
544
545     public void addResizeFrame(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
546         clearResizeFrame();
547
548         mCurrentResizeFrame = (AppWidgetResizeFrame) LayoutInflater.from(mLauncher)
549                 .inflate(R.layout.app_widget_resize_frame, this, false);
550         mCurrentResizeFrame.setupForWidget(widget, cellLayout, this);
551         ((LayoutParams) mCurrentResizeFrame.getLayoutParams()).customPosition = true;
552
553         addView(mCurrentResizeFrame);
554         mCurrentResizeFrame.snapToWidget(false);
555     }
556
557     public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
558             float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
559             int duration) {
560         Rect r = new Rect();
561         getViewRectRelativeToSelf(dragView, r);
562         final int fromX = r.left;
563         final int fromY = r.top;
564
565         animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
566                 onFinishRunnable, animationEndStyle, duration, null);
567     }
568
569     public void animateViewIntoPosition(DragView dragView, final View child,
570             final Runnable onFinishAnimationRunnable, View anchorView) {
571         animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, anchorView);
572     }
573
574     public void animateViewIntoPosition(DragView dragView, final View child, int duration,
575             final Runnable onFinishAnimationRunnable, View anchorView) {
576         ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
577         CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
578         parentChildren.measureChild(child);
579
580         Rect r = new Rect();
581         getViewRectRelativeToSelf(dragView, r);
582
583         int coord[] = new int[2];
584         float childScale = child.getScaleX();
585         coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2);
586         coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2);
587
588         // Since the child hasn't necessarily been laid out, we force the lp to be updated with
589         // the correct coordinates (above) and use these to determine the final location
590         float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
591         // We need to account for the scale of the child itself, as the above only accounts for
592         // for the scale in parents.
593         scale *= childScale;
594         int toX = coord[0];
595         int toY = coord[1];
596         float toScale = scale;
597         if (child instanceof TextView) {
598             TextView tv = (TextView) child;
599             // Account for the source scale of the icon (ie. from AllApps to Workspace, in which
600             // the workspace may have smaller icon bounds).
601             toScale = scale / dragView.getIntrinsicIconScaleFactor();
602
603             // The child may be scaled (always about the center of the view) so to account for it,
604             // we have to offset the position by the scaled size.  Once we do that, we can center
605             // the drag view about the scaled child view.
606             toY += Math.round(toScale * tv.getPaddingTop());
607             toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
608             if (dragView.getDragVisualizeOffset() != null) {
609                 toY -=  Math.round(toScale * dragView.getDragVisualizeOffset().y);
610             }
611
612             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
613         } else if (child instanceof FolderIcon) {
614             // Account for holographic blur padding on the drag view
615             toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
616             toY -= scale * dragView.getBlurSizeOutline() / 2;
617             toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
618             // Center in the x coordinate about the target's drawable
619             toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
620         } else {
621             toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
622             toX -= (Math.round(scale * (dragView.getMeasuredWidth()
623                     - child.getMeasuredWidth()))) / 2;
624         }
625
626         final int fromX = r.left;
627         final int fromY = r.top;
628         child.setVisibility(INVISIBLE);
629         Runnable onCompleteRunnable = new Runnable() {
630             public void run() {
631                 child.setVisibility(VISIBLE);
632                 if (onFinishAnimationRunnable != null) {
633                     onFinishAnimationRunnable.run();
634                 }
635             }
636         };
637         animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
638                 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
639     }
640
641     public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
642             final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
643             float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
644             int animationEndStyle, int duration, View anchorView) {
645         Rect from = new Rect(fromX, fromY, fromX +
646                 view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
647         Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
648         animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
649                 null, null, onCompleteRunnable, animationEndStyle, anchorView);
650     }
651
652     /**
653      * This method animates a view at the end of a drag and drop animation.
654      *
655      * @param view The view to be animated. This view is drawn directly into DragLayer, and so
656      *        doesn't need to be a child of DragLayer.
657      * @param from The initial location of the view. Only the left and top parameters are used.
658      * @param to The final location of the view. Only the left and top parameters are used. This
659      *        location doesn't account for scaling, and so should be centered about the desired
660      *        final location (including scaling).
661      * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
662      * @param finalScaleX The final scale of the view. The view is scaled about its center.
663      * @param finalScaleY The final scale of the view. The view is scaled about its center.
664      * @param duration The duration of the animation.
665      * @param motionInterpolator The interpolator to use for the location of the view.
666      * @param alphaInterpolator The interpolator to use for the alpha of the view.
667      * @param onCompleteRunnable Optional runnable to run on animation completion.
668      * @param animationEndStyle Whether or not to fade out the view once the animation completes.
669      *        {@link #ANIMATION_END_DISAPPEAR} or {@link #ANIMATION_END_REMAIN_VISIBLE}.
670      * @param anchorView If not null, this represents the view which the animated view stays
671      *        anchored to in case scrolling is currently taking place. Note: currently this is
672      *        only used for the X dimension for the case of the workspace.
673      */
674     public void animateView(final DragView view, final Rect from, final Rect to,
675             final float finalAlpha, final float initScaleX, final float initScaleY,
676             final float finalScaleX, final float finalScaleY, int duration,
677             final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
678             final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
679
680         // Calculate the duration of the animation based on the object's distance
681         final float dist = (float) Math.hypot(to.left - from.left, to.top - from.top);
682         final Resources res = getResources();
683         final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
684
685         // If duration < 0, this is a cue to compute the duration based on the distance
686         if (duration < 0) {
687             duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
688             if (dist < maxDist) {
689                 duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
690             }
691             duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
692         }
693
694         // Fall back to cubic ease out interpolator for the animation if none is specified
695         TimeInterpolator interpolator = null;
696         if (alphaInterpolator == null || motionInterpolator == null) {
697             interpolator = mCubicEaseOutInterpolator;
698         }
699
700         // Animate the view
701         final float initAlpha = view.getAlpha();
702         final float dropViewScale = view.getScaleX();
703         AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
704             @Override
705             public void onAnimationUpdate(ValueAnimator animation) {
706                 final float percent = (Float) animation.getAnimatedValue();
707                 final int width = view.getMeasuredWidth();
708                 final int height = view.getMeasuredHeight();
709
710                 float alphaPercent = alphaInterpolator == null ? percent :
711                         alphaInterpolator.getInterpolation(percent);
712                 float motionPercent = motionInterpolator == null ? percent :
713                         motionInterpolator.getInterpolation(percent);
714
715                 float initialScaleX = initScaleX * dropViewScale;
716                 float initialScaleY = initScaleY * dropViewScale;
717                 float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
718                 float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
719                 float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
720
721                 float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
722                 float fromTop = from.top + (initialScaleY - 1f) * height / 2;
723
724                 int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
725                 int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
726
727                 int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() *
728                     (mAnchorViewInitialScrollX - mAnchorView.getScrollX()));
729
730                 int xPos = x - mDropView.getScrollX() + anchorAdjust;
731                 int yPos = y - mDropView.getScrollY();
732
733                 mDropView.setTranslationX(xPos);
734                 mDropView.setTranslationY(yPos);
735                 mDropView.setScaleX(scaleX);
736                 mDropView.setScaleY(scaleY);
737                 mDropView.setAlpha(alpha);
738             }
739         };
740         animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
741                 anchorView);
742     }
743
744     public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
745             TimeInterpolator interpolator, final Runnable onCompleteRunnable,
746             final int animationEndStyle, View anchorView) {
747         // Clean up the previous animations
748         if (mDropAnim != null) mDropAnim.cancel();
749
750         // Show the drop view if it was previously hidden
751         mDropView = view;
752         mDropView.cancelAnimation();
753         mDropView.requestLayout();
754
755         // Set the anchor view if the page is scrolling
756         if (anchorView != null) {
757             mAnchorViewInitialScrollX = anchorView.getScrollX();
758         }
759         mAnchorView = anchorView;
760
761         // Create and start the animation
762         mDropAnim = new ValueAnimator();
763         mDropAnim.setInterpolator(interpolator);
764         mDropAnim.setDuration(duration);
765         mDropAnim.setFloatValues(0f, 1f);
766         mDropAnim.addUpdateListener(updateCb);
767         mDropAnim.addListener(new AnimatorListenerAdapter() {
768             public void onAnimationEnd(Animator animation) {
769                 if (onCompleteRunnable != null) {
770                     onCompleteRunnable.run();
771                 }
772                 switch (animationEndStyle) {
773                 case ANIMATION_END_DISAPPEAR:
774                     clearAnimatedView();
775                     break;
776                 case ANIMATION_END_REMAIN_VISIBLE:
777                     break;
778                 }
779             }
780         });
781         mDropAnim.start();
782     }
783
784     public void clearAnimatedView() {
785         if (mDropAnim != null) {
786             mDropAnim.cancel();
787         }
788         if (mDropView != null) {
789             mDragController.onDeferredEndDrag(mDropView);
790         }
791         mDropView = null;
792         invalidate();
793     }
794
795     public View getAnimatedView() {
796         return mDropView;
797     }
798
799     @Override
800     public void onChildViewAdded(View parent, View child) {
801         super.onChildViewAdded(parent, child);
802         updateChildIndices();
803     }
804
805     @Override
806     public void onChildViewRemoved(View parent, View child) {
807         updateChildIndices();
808     }
809
810     @Override
811     public void bringChildToFront(View child) {
812         super.bringChildToFront(child);
813         updateChildIndices();
814     }
815
816     private void updateChildIndices() {
817         mTopViewIndex = -1;
818         int childCount = getChildCount();
819         for (int i = 0; i < childCount; i++) {
820             if (getChildAt(i) instanceof DragView) {
821                 mTopViewIndex = i;
822             }
823         }
824         mChildCountOnLastUpdate = childCount;
825     }
826
827     @Override
828     protected int getChildDrawingOrder(int childCount, int i) {
829         if (mChildCountOnLastUpdate != childCount) {
830             // between platform versions 17 and 18, behavior for onChildViewRemoved / Added changed.
831             // Pre-18, the child was not added / removed by the time of those callbacks. We need to
832             // force update our representation of things here to avoid crashing on pre-18 devices
833             // in certain instances.
834             updateChildIndices();
835         }
836
837         // i represents the current draw iteration
838         if (mTopViewIndex == -1) {
839             // in general we do nothing
840             return i;
841         } else if (i == childCount - 1) {
842             // if we have a top index, we return it when drawing last item (highest z-order)
843             return mTopViewIndex;
844         } else if (i < mTopViewIndex) {
845             return i;
846         } else {
847             // for indexes greater than the top index, we fetch one item above to shift for the
848             // displacement of the top index
849             return i + 1;
850         }
851     }
852
853     public void invalidateScrim() {
854         if (mBackgroundAlpha > 0.0f) {
855             invalidate();
856         }
857     }
858
859     @Override
860     protected void dispatchDraw(Canvas canvas) {
861         // Draw the background below children.
862         if (mBackgroundAlpha > 0.0f) {
863             // Update the scroll position first to ensure scrim cutout is in the right place.
864             mLauncher.getWorkspace().computeScrollWithoutInvalidation();
865
866             int alpha = (int) (mBackgroundAlpha * 255);
867             CellLayout currCellLayout = mLauncher.getWorkspace().getCurrentDragOverlappingLayout();
868             canvas.save();
869             if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) {
870                 // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
871                 getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
872                 canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
873             }
874             canvas.drawColor((alpha << 24) | SCRIM_COLOR);
875             canvas.restore();
876         }
877
878         mFocusIndicatorHelper.draw(canvas);
879         super.dispatchDraw(canvas);
880     }
881
882     public void setBackgroundAlpha(float alpha) {
883         if (alpha != mBackgroundAlpha) {
884             mBackgroundAlpha = alpha;
885             invalidate();
886         }
887     }
888
889     public float getBackgroundAlpha() {
890         return mBackgroundAlpha;
891     }
892
893     @Override
894     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
895         View topView = AbstractFloatingView.getTopOpenView(mLauncher);
896         if (topView != null) {
897             return topView.requestFocus(direction, previouslyFocusedRect);
898         } else {
899             return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
900         }
901     }
902
903     @Override
904     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
905         View topView = AbstractFloatingView.getTopOpenView(mLauncher);
906         if (topView != null) {
907             topView.addFocusables(views, direction);
908         } else {
909             super.addFocusables(views, direction, focusableMode);
910         }
911     }
912
913     public void setTouchCompleteListener(TouchCompleteListener listener) {
914         mTouchCompleteListener = listener;
915     }
916
917     public interface TouchCompleteListener {
918         public void onTouchComplete();
919     }
920 }