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