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