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.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      */
    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 
    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 
    146     public ViewGroupFocusHelper getFocusIndicatorHelper() {
    147         return mFocusIndicatorHelper;
    148     }
    149 
    150     @Override
    151     public boolean dispatchKeyEvent(KeyEvent event) {
    152         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
    153     }
    154 
    155     public void onAccessibilityStateChanged(boolean isAccessibilityEnabled) {
    156         mPinchListener = FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW || isAccessibilityEnabled
    157                 ? null : new PinchToOverviewListener(mLauncher);
    158     }
    159 
    160     public boolean isEventOverPageIndicator(MotionEvent ev) {
    161         return isEventOverView(mLauncher.getWorkspace().getPageIndicator(), ev);
    162     }
    163 
    164     public boolean isEventOverHotseat(MotionEvent ev) {
    165         return isEventOverView(mLauncher.getHotseat(), ev);
    166     }
    167 
    168     private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
    169         return isEventOverView(folder, ev);
    170     }
    171 
    172     private boolean isEventOverDropTargetBar(MotionEvent ev) {
    173         return isEventOverView(mLauncher.getDropTargetBar(), ev);
    174     }
    175 
    176     public boolean isEventOverView(View view, MotionEvent ev) {
    177         getDescendantRectRelativeToSelf(view, mHitRect);
    178         return mHitRect.contains((int) ev.getX(), (int) ev.getY());
    179     }
    180 
    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
    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
    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 
    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 
    311     private boolean isInAccessibleDrag() {
    312         return mLauncher.getAccessibilityDelegate().isInAccessibleDrag();
    313     }
    314 
    315     @Override
    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
    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
    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
    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      */
    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 
    394     public float getLocationInDragLayer(View child, int[] loc) {
    395         loc[0] = 0;
    396         loc[1] = 0;
    397         return getDescendantCoordRelativeToSelf(child, loc);
    398     }
    399 
    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      */
    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      */
    425     public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
    426         Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
    427     }
    428 
    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
    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
    452     public void setInsets(Rect insets) {
    453         super.setInsets(insets);
    454         setBackgroundResource(insets.top == 0 ? 0 : R.drawable.workspace_bg);
    455     }
    456 
    457     @Override
    458     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    459         return new LayoutParams(getContext(), attrs);
    460     }
    461 
    462     @Override
    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
    469     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    470         return p instanceof LayoutParams;
    471     }
    472 
    473     @Override
    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 
    482         public LayoutParams(Context c, AttributeSet attrs) {
    483             super(c, attrs);
    484         }
    485 
    486         public LayoutParams(int width, int height) {
    487             super(width, height);
    488         }
    489 
    490         public LayoutParams(ViewGroup.LayoutParams lp) {
    491             super(lp);
    492         }
    493 
    494         public void setWidth(int width) {
    495             this.width = width;
    496         }
    497 
    498         public int getWidth() {
    499             return width;
    500         }
    501 
    502         public void setHeight(int height) {
    503             this.height = height;
    504         }
    505 
    506         public int getHeight() {
    507             return height;
    508         }
    509 
    510         public void setX(int x) {
    511             this.x = x;
    512         }
    513 
    514         public int getX() {
    515             return x;
    516         }
    517 
    518         public void setY(int y) {
    519             this.y = y;
    520         }
    521 
    522         public int getY() {
    523             return y;
    524         }
    525     }
    526 
    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 
    542     public void clearResizeFrame() {
    543         if (mCurrentResizeFrame != null) {
    544             mCurrentResizeFrame.commitResize();
    545             removeView(mCurrentResizeFrame);
    546             mCurrentResizeFrame = null;
    547         }
    548     }
    549 
    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 
    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 
    574     public void animateViewIntoPosition(DragView dragView, final View child,
    575             final Runnable onFinishAnimationRunnable, View anchorView) {
    576         animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, anchorView);
    577     }
    578 
    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 
    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      */
    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 
    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 
    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 
    800     public View getAnimatedView() {
    801         return mDropView;
    802     }
    803 
    804     @Override
    805     public void onChildViewAdded(View parent, View child) {
    806         super.onChildViewAdded(parent, child);
    807         updateChildIndices();
    808     }
    809 
    810     @Override
    811     public void onChildViewRemoved(View parent, View child) {
    812         updateChildIndices();
    813     }
    814 
    815     @Override
    816     public void bringChildToFront(View child) {
    817         super.bringChildToFront(child);
    818         updateChildIndices();
    819     }
    820 
    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
    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 
    858     public void invalidateScrim() {
    859         if (mBackgroundAlpha > 0.0f) {
    860             invalidate();
    861         }
    862     }
    863 
    864     @Override
    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 
    887     public void setBackgroundAlpha(float alpha) {
    888         if (alpha != mBackgroundAlpha) {
    889             mBackgroundAlpha = alpha;
    890             invalidate();
    891         }
    892     }
    893 
    894     public float getBackgroundAlpha() {
    895         return mBackgroundAlpha;
    896     }
    897 
    898     @Override
    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
    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 
    918     public void setTouchCompleteListener(TouchCompleteListener listener) {
    919         mTouchCompleteListener = listener;
    920     }
    921 
    922     public interface TouchCompleteListener {
    923         public void onTouchComplete();
    924     }
    925 }
    926