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