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