Home | History | Annotate | Download | only in launcher2
      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.launcher2;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.AnimatorSet;
     22 import android.animation.TimeInterpolator;
     23 import android.animation.ValueAnimator;
     24 import android.animation.ValueAnimator.AnimatorUpdateListener;
     25 import android.content.Context;
     26 import android.content.res.Resources;
     27 import android.content.res.TypedArray;
     28 import android.graphics.Bitmap;
     29 import android.graphics.Canvas;
     30 import android.graphics.Color;
     31 import android.graphics.Paint;
     32 import android.graphics.Point;
     33 import android.graphics.PorterDuff;
     34 import android.graphics.PorterDuffXfermode;
     35 import android.graphics.Rect;
     36 import android.graphics.drawable.ColorDrawable;
     37 import android.graphics.drawable.Drawable;
     38 import android.graphics.drawable.NinePatchDrawable;
     39 import android.os.Parcelable;
     40 import android.util.AttributeSet;
     41 import android.util.Log;
     42 import android.util.SparseArray;
     43 import android.view.MotionEvent;
     44 import android.view.View;
     45 import android.view.ViewDebug;
     46 import android.view.ViewGroup;
     47 import android.view.animation.Animation;
     48 import android.view.animation.DecelerateInterpolator;
     49 import android.view.animation.LayoutAnimationController;
     50 
     51 import com.android.launcher.R;
     52 import com.android.launcher2.FolderIcon.FolderRingAnimator;
     53 
     54 import java.util.ArrayList;
     55 import java.util.Arrays;
     56 import java.util.Collections;
     57 import java.util.Comparator;
     58 import java.util.HashMap;
     59 import java.util.Stack;
     60 
     61 public class CellLayout extends ViewGroup {
     62     static final String TAG = "CellLayout";
     63 
     64     private Launcher mLauncher;
     65     private int mCellWidth;
     66     private int mCellHeight;
     67 
     68     private int mCountX;
     69     private int mCountY;
     70 
     71     private int mOriginalWidthGap;
     72     private int mOriginalHeightGap;
     73     private int mWidthGap;
     74     private int mHeightGap;
     75     private int mMaxGap;
     76     private boolean mScrollingTransformsDirty = false;
     77 
     78     private final Rect mRect = new Rect();
     79     private final CellInfo mCellInfo = new CellInfo();
     80 
     81     // These are temporary variables to prevent having to allocate a new object just to
     82     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
     83     private final int[] mTmpXY = new int[2];
     84     private final int[] mTmpPoint = new int[2];
     85     int[] mTempLocation = new int[2];
     86 
     87     boolean[][] mOccupied;
     88     boolean[][] mTmpOccupied;
     89     private boolean mLastDownOnOccupiedCell = false;
     90 
     91     private OnTouchListener mInterceptTouchListener;
     92 
     93     private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
     94     private int[] mFolderLeaveBehindCell = {-1, -1};
     95 
     96     private int mForegroundAlpha = 0;
     97     private float mBackgroundAlpha;
     98     private float mBackgroundAlphaMultiplier = 1.0f;
     99 
    100     private Drawable mNormalBackground;
    101     private Drawable mActiveGlowBackground;
    102     private Drawable mOverScrollForegroundDrawable;
    103     private Drawable mOverScrollLeft;
    104     private Drawable mOverScrollRight;
    105     private Rect mBackgroundRect;
    106     private Rect mForegroundRect;
    107     private int mForegroundPadding;
    108 
    109     // If we're actively dragging something over this screen, mIsDragOverlapping is true
    110     private boolean mIsDragOverlapping = false;
    111     private final Point mDragCenter = new Point();
    112 
    113     // These arrays are used to implement the drag visualization on x-large screens.
    114     // They are used as circular arrays, indexed by mDragOutlineCurrent.
    115     private Rect[] mDragOutlines = new Rect[4];
    116     private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
    117     private InterruptibleInOutAnimator[] mDragOutlineAnims =
    118             new InterruptibleInOutAnimator[mDragOutlines.length];
    119 
    120     // Used as an index into the above 3 arrays; indicates which is the most current value.
    121     private int mDragOutlineCurrent = 0;
    122     private final Paint mDragOutlinePaint = new Paint();
    123 
    124     private BubbleTextView mPressedOrFocusedIcon;
    125 
    126     private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
    127             HashMap<CellLayout.LayoutParams, Animator>();
    128     private HashMap<View, ReorderHintAnimation>
    129             mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
    130 
    131     private boolean mItemPlacementDirty = false;
    132 
    133     // When a drag operation is in progress, holds the nearest cell to the touch point
    134     private final int[] mDragCell = new int[2];
    135 
    136     private boolean mDragging = false;
    137 
    138     private TimeInterpolator mEaseOutInterpolator;
    139     private ShortcutAndWidgetContainer mShortcutsAndWidgets;
    140 
    141     private boolean mIsHotseat = false;
    142     private float mHotseatScale = 1f;
    143 
    144     public static final int MODE_DRAG_OVER = 0;
    145     public static final int MODE_ON_DROP = 1;
    146     public static final int MODE_ON_DROP_EXTERNAL = 2;
    147     public static final int MODE_ACCEPT_DROP = 3;
    148     private static final boolean DESTRUCTIVE_REORDER = false;
    149     private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
    150 
    151     static final int LANDSCAPE = 0;
    152     static final int PORTRAIT = 1;
    153 
    154     private static final float REORDER_HINT_MAGNITUDE = 0.12f;
    155     private static final int REORDER_ANIMATION_DURATION = 150;
    156     private float mReorderHintAnimationMagnitude;
    157 
    158     private ArrayList<View> mIntersectingViews = new ArrayList<View>();
    159     private Rect mOccupiedRect = new Rect();
    160     private int[] mDirectionVector = new int[2];
    161     int[] mPreviousReorderDirection = new int[2];
    162     private static final int INVALID_DIRECTION = -100;
    163     private DropTarget.DragEnforcer mDragEnforcer;
    164 
    165     private final static PorterDuffXfermode sAddBlendMode =
    166             new PorterDuffXfermode(PorterDuff.Mode.ADD);
    167     private final static Paint sPaint = new Paint();
    168 
    169     public CellLayout(Context context) {
    170         this(context, null);
    171     }
    172 
    173     public CellLayout(Context context, AttributeSet attrs) {
    174         this(context, attrs, 0);
    175     }
    176 
    177     public CellLayout(Context context, AttributeSet attrs, int defStyle) {
    178         super(context, attrs, defStyle);
    179         mDragEnforcer = new DropTarget.DragEnforcer(context);
    180 
    181         // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
    182         // the user where a dragged item will land when dropped.
    183         setWillNotDraw(false);
    184         setClipToPadding(false);
    185         mLauncher = (Launcher) context;
    186 
    187         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
    188 
    189         mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
    190         mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
    191         mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0);
    192         mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0);
    193         mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0);
    194         mCountX = LauncherModel.getCellCountX();
    195         mCountY = LauncherModel.getCellCountY();
    196         mOccupied = new boolean[mCountX][mCountY];
    197         mTmpOccupied = new boolean[mCountX][mCountY];
    198         mPreviousReorderDirection[0] = INVALID_DIRECTION;
    199         mPreviousReorderDirection[1] = INVALID_DIRECTION;
    200 
    201         a.recycle();
    202 
    203         setAlwaysDrawnWithCacheEnabled(false);
    204 
    205         final Resources res = getResources();
    206         mHotseatScale = (res.getInteger(R.integer.hotseat_item_scale_percentage) / 100f);
    207 
    208         mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo);
    209         mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
    210 
    211         mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
    212         mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
    213         mForegroundPadding =
    214                 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
    215 
    216         mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
    217                 res.getDimensionPixelSize(R.dimen.app_icon_size));
    218 
    219         mNormalBackground.setFilterBitmap(true);
    220         mActiveGlowBackground.setFilterBitmap(true);
    221 
    222         // Initialize the data structures used for the drag visualization.
    223 
    224         mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
    225 
    226 
    227         mDragCell[0] = mDragCell[1] = -1;
    228         for (int i = 0; i < mDragOutlines.length; i++) {
    229             mDragOutlines[i] = new Rect(-1, -1, -1, -1);
    230         }
    231 
    232         // When dragging things around the home screens, we show a green outline of
    233         // where the item will land. The outlines gradually fade out, leaving a trail
    234         // behind the drag path.
    235         // Set up all the animations that are used to implement this fading.
    236         final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
    237         final float fromAlphaValue = 0;
    238         final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
    239 
    240         Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
    241 
    242         for (int i = 0; i < mDragOutlineAnims.length; i++) {
    243             final InterruptibleInOutAnimator anim =
    244                 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
    245             anim.getAnimator().setInterpolator(mEaseOutInterpolator);
    246             final int thisIndex = i;
    247             anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
    248                 public void onAnimationUpdate(ValueAnimator animation) {
    249                     final Bitmap outline = (Bitmap)anim.getTag();
    250 
    251                     // If an animation is started and then stopped very quickly, we can still
    252                     // get spurious updates we've cleared the tag. Guard against this.
    253                     if (outline == null) {
    254                         @SuppressWarnings("all") // suppress dead code warning
    255                         final boolean debug = false;
    256                         if (debug) {
    257                             Object val = animation.getAnimatedValue();
    258                             Log.d(TAG, "anim " + thisIndex + " update: " + val +
    259                                      ", isStopped " + anim.isStopped());
    260                         }
    261                         // Try to prevent it from continuing to run
    262                         animation.cancel();
    263                     } else {
    264                         mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
    265                         CellLayout.this.invalidate(mDragOutlines[thisIndex]);
    266                     }
    267                 }
    268             });
    269             // The animation holds a reference to the drag outline bitmap as long is it's
    270             // running. This way the bitmap can be GCed when the animations are complete.
    271             anim.getAnimator().addListener(new AnimatorListenerAdapter() {
    272                 @Override
    273                 public void onAnimationEnd(Animator animation) {
    274                     if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
    275                         anim.setTag(null);
    276                     }
    277                 }
    278             });
    279             mDragOutlineAnims[i] = anim;
    280         }
    281 
    282         mBackgroundRect = new Rect();
    283         mForegroundRect = new Rect();
    284 
    285         mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
    286         mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
    287                 mCountX);
    288 
    289         addView(mShortcutsAndWidgets);
    290     }
    291 
    292     static int widthInPortrait(Resources r, int numCells) {
    293         // We use this method from Workspace to figure out how many rows/columns Launcher should
    294         // have. We ignore the left/right padding on CellLayout because it turns out in our design
    295         // the padding extends outside the visible screen size, but it looked fine anyway.
    296         int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
    297         int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
    298                 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
    299 
    300         return  minGap * (numCells - 1) + cellWidth * numCells;
    301     }
    302 
    303     static int heightInLandscape(Resources r, int numCells) {
    304         // We use this method from Workspace to figure out how many rows/columns Launcher should
    305         // have. We ignore the left/right padding on CellLayout because it turns out in our design
    306         // the padding extends outside the visible screen size, but it looked fine anyway.
    307         int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
    308         int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
    309                 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
    310 
    311         return minGap * (numCells - 1) + cellHeight * numCells;
    312     }
    313 
    314     public void enableHardwareLayers() {
    315         mShortcutsAndWidgets.setLayerType(LAYER_TYPE_HARDWARE, sPaint);
    316     }
    317 
    318     public void disableHardwareLayers() {
    319         mShortcutsAndWidgets.setLayerType(LAYER_TYPE_NONE, sPaint);
    320     }
    321 
    322     public void buildHardwareLayer() {
    323         mShortcutsAndWidgets.buildLayer();
    324     }
    325 
    326     public float getChildrenScale() {
    327         return mIsHotseat ? mHotseatScale : 1.0f;
    328     }
    329 
    330     public void setGridSize(int x, int y) {
    331         mCountX = x;
    332         mCountY = y;
    333         mOccupied = new boolean[mCountX][mCountY];
    334         mTmpOccupied = new boolean[mCountX][mCountY];
    335         mTempRectStack.clear();
    336         mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
    337                 mCountX);
    338         requestLayout();
    339     }
    340 
    341     // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
    342     public void setInvertIfRtl(boolean invert) {
    343         mShortcutsAndWidgets.setInvertIfRtl(invert);
    344     }
    345 
    346     private void invalidateBubbleTextView(BubbleTextView icon) {
    347         final int padding = icon.getPressedOrFocusedBackgroundPadding();
    348         invalidate(icon.getLeft() + getPaddingLeft() - padding,
    349                 icon.getTop() + getPaddingTop() - padding,
    350                 icon.getRight() + getPaddingLeft() + padding,
    351                 icon.getBottom() + getPaddingTop() + padding);
    352     }
    353 
    354     void setOverScrollAmount(float r, boolean left) {
    355         if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
    356             mOverScrollForegroundDrawable = mOverScrollLeft;
    357         } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
    358             mOverScrollForegroundDrawable = mOverScrollRight;
    359         }
    360 
    361         mForegroundAlpha = (int) Math.round((r * 255));
    362         mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
    363         invalidate();
    364     }
    365 
    366     void setPressedOrFocusedIcon(BubbleTextView icon) {
    367         // We draw the pressed or focused BubbleTextView's background in CellLayout because it
    368         // requires an expanded clip rect (due to the glow's blur radius)
    369         BubbleTextView oldIcon = mPressedOrFocusedIcon;
    370         mPressedOrFocusedIcon = icon;
    371         if (oldIcon != null) {
    372             invalidateBubbleTextView(oldIcon);
    373         }
    374         if (mPressedOrFocusedIcon != null) {
    375             invalidateBubbleTextView(mPressedOrFocusedIcon);
    376         }
    377     }
    378 
    379     void setIsDragOverlapping(boolean isDragOverlapping) {
    380         if (mIsDragOverlapping != isDragOverlapping) {
    381             mIsDragOverlapping = isDragOverlapping;
    382             invalidate();
    383         }
    384     }
    385 
    386     boolean getIsDragOverlapping() {
    387         return mIsDragOverlapping;
    388     }
    389 
    390     protected void setOverscrollTransformsDirty(boolean dirty) {
    391         mScrollingTransformsDirty = dirty;
    392     }
    393 
    394     protected void resetOverscrollTransforms() {
    395         if (mScrollingTransformsDirty) {
    396             setOverscrollTransformsDirty(false);
    397             setTranslationX(0);
    398             setRotationY(0);
    399             // It doesn't matter if we pass true or false here, the important thing is that we
    400             // pass 0, which results in the overscroll drawable not being drawn any more.
    401             setOverScrollAmount(0, false);
    402             setPivotX(getMeasuredWidth() / 2);
    403             setPivotY(getMeasuredHeight() / 2);
    404         }
    405     }
    406 
    407     public void scaleRect(Rect r, float scale) {
    408         if (scale != 1.0f) {
    409             r.left = (int) (r.left * scale + 0.5f);
    410             r.top = (int) (r.top * scale + 0.5f);
    411             r.right = (int) (r.right * scale + 0.5f);
    412             r.bottom = (int) (r.bottom * scale + 0.5f);
    413         }
    414     }
    415 
    416     Rect temp = new Rect();
    417     void scaleRectAboutCenter(Rect in, Rect out, float scale) {
    418         int cx = in.centerX();
    419         int cy = in.centerY();
    420         out.set(in);
    421         out.offset(-cx, -cy);
    422         scaleRect(out, scale);
    423         out.offset(cx, cy);
    424     }
    425 
    426     @Override
    427     protected void onDraw(Canvas canvas) {
    428         // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
    429         // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
    430         // When we're small, we are either drawn normally or in the "accepts drops" state (during
    431         // a drag). However, we also drag the mini hover background *over* one of those two
    432         // backgrounds
    433         if (mBackgroundAlpha > 0.0f) {
    434             Drawable bg;
    435 
    436             if (mIsDragOverlapping) {
    437                 // In the mini case, we draw the active_glow bg *over* the active background
    438                 bg = mActiveGlowBackground;
    439             } else {
    440                 bg = mNormalBackground;
    441             }
    442 
    443             bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
    444             bg.setBounds(mBackgroundRect);
    445             bg.draw(canvas);
    446         }
    447 
    448         final Paint paint = mDragOutlinePaint;
    449         for (int i = 0; i < mDragOutlines.length; i++) {
    450             final float alpha = mDragOutlineAlphas[i];
    451             if (alpha > 0) {
    452                 final Rect r = mDragOutlines[i];
    453                 scaleRectAboutCenter(r, temp, getChildrenScale());
    454                 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
    455                 paint.setAlpha((int)(alpha + .5f));
    456                 canvas.drawBitmap(b, null, temp, paint);
    457             }
    458         }
    459 
    460         // We draw the pressed or focused BubbleTextView's background in CellLayout because it
    461         // requires an expanded clip rect (due to the glow's blur radius)
    462         if (mPressedOrFocusedIcon != null) {
    463             final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
    464             final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
    465             if (b != null) {
    466                 canvas.drawBitmap(b,
    467                         mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
    468                         mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
    469                         null);
    470             }
    471         }
    472 
    473         if (DEBUG_VISUALIZE_OCCUPIED) {
    474             int[] pt = new int[2];
    475             ColorDrawable cd = new ColorDrawable(Color.RED);
    476             cd.setBounds(0, 0,  mCellWidth, mCellHeight);
    477             for (int i = 0; i < mCountX; i++) {
    478                 for (int j = 0; j < mCountY; j++) {
    479                     if (mOccupied[i][j]) {
    480                         cellToPoint(i, j, pt);
    481                         canvas.save();
    482                         canvas.translate(pt[0], pt[1]);
    483                         cd.draw(canvas);
    484                         canvas.restore();
    485                     }
    486                 }
    487             }
    488         }
    489 
    490         int previewOffset = FolderRingAnimator.sPreviewSize;
    491 
    492         // The folder outer / inner ring image(s)
    493         for (int i = 0; i < mFolderOuterRings.size(); i++) {
    494             FolderRingAnimator fra = mFolderOuterRings.get(i);
    495 
    496             // Draw outer ring
    497             Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
    498             int width = (int) fra.getOuterRingSize();
    499             int height = width;
    500             cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
    501 
    502             int centerX = mTempLocation[0] + mCellWidth / 2;
    503             int centerY = mTempLocation[1] + previewOffset / 2;
    504 
    505             canvas.save();
    506             canvas.translate(centerX - width / 2, centerY - height / 2);
    507             d.setBounds(0, 0, width, height);
    508             d.draw(canvas);
    509             canvas.restore();
    510 
    511             // Draw inner ring
    512             d = FolderRingAnimator.sSharedInnerRingDrawable;
    513             width = (int) fra.getInnerRingSize();
    514             height = width;
    515             cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
    516 
    517             centerX = mTempLocation[0] + mCellWidth / 2;
    518             centerY = mTempLocation[1] + previewOffset / 2;
    519             canvas.save();
    520             canvas.translate(centerX - width / 2, centerY - width / 2);
    521             d.setBounds(0, 0, width, height);
    522             d.draw(canvas);
    523             canvas.restore();
    524         }
    525 
    526         if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
    527             Drawable d = FolderIcon.sSharedFolderLeaveBehind;
    528             int width = d.getIntrinsicWidth();
    529             int height = d.getIntrinsicHeight();
    530 
    531             cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
    532             int centerX = mTempLocation[0] + mCellWidth / 2;
    533             int centerY = mTempLocation[1] + previewOffset / 2;
    534 
    535             canvas.save();
    536             canvas.translate(centerX - width / 2, centerY - width / 2);
    537             d.setBounds(0, 0, width, height);
    538             d.draw(canvas);
    539             canvas.restore();
    540         }
    541     }
    542 
    543     @Override
    544     protected void dispatchDraw(Canvas canvas) {
    545         super.dispatchDraw(canvas);
    546         if (mForegroundAlpha > 0) {
    547             mOverScrollForegroundDrawable.setBounds(mForegroundRect);
    548             Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
    549             p.setXfermode(sAddBlendMode);
    550             mOverScrollForegroundDrawable.draw(canvas);
    551             p.setXfermode(null);
    552         }
    553     }
    554 
    555     public void showFolderAccept(FolderRingAnimator fra) {
    556         mFolderOuterRings.add(fra);
    557     }
    558 
    559     public void hideFolderAccept(FolderRingAnimator fra) {
    560         if (mFolderOuterRings.contains(fra)) {
    561             mFolderOuterRings.remove(fra);
    562         }
    563         invalidate();
    564     }
    565 
    566     public void setFolderLeaveBehindCell(int x, int y) {
    567         mFolderLeaveBehindCell[0] = x;
    568         mFolderLeaveBehindCell[1] = y;
    569         invalidate();
    570     }
    571 
    572     public void clearFolderLeaveBehind() {
    573         mFolderLeaveBehindCell[0] = -1;
    574         mFolderLeaveBehindCell[1] = -1;
    575         invalidate();
    576     }
    577 
    578     @Override
    579     public boolean shouldDelayChildPressedState() {
    580         return false;
    581     }
    582 
    583     public void restoreInstanceState(SparseArray<Parcelable> states) {
    584         dispatchRestoreInstanceState(states);
    585     }
    586 
    587     @Override
    588     public void cancelLongPress() {
    589         super.cancelLongPress();
    590 
    591         // Cancel long press for all children
    592         final int count = getChildCount();
    593         for (int i = 0; i < count; i++) {
    594             final View child = getChildAt(i);
    595             child.cancelLongPress();
    596         }
    597     }
    598 
    599     public void setOnInterceptTouchListener(View.OnTouchListener listener) {
    600         mInterceptTouchListener = listener;
    601     }
    602 
    603     int getCountX() {
    604         return mCountX;
    605     }
    606 
    607     int getCountY() {
    608         return mCountY;
    609     }
    610 
    611     public void setIsHotseat(boolean isHotseat) {
    612         mIsHotseat = isHotseat;
    613     }
    614 
    615     public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
    616             boolean markCells) {
    617         final LayoutParams lp = params;
    618 
    619         // Hotseat icons - remove text
    620         if (child instanceof BubbleTextView) {
    621             BubbleTextView bubbleChild = (BubbleTextView) child;
    622 
    623             Resources res = getResources();
    624             if (mIsHotseat) {
    625                 bubbleChild.setTextColor(res.getColor(android.R.color.transparent));
    626             } else {
    627                 bubbleChild.setTextColor(res.getColor(R.color.workspace_icon_text_color));
    628             }
    629         }
    630 
    631         child.setScaleX(getChildrenScale());
    632         child.setScaleY(getChildrenScale());
    633 
    634         // Generate an id for each view, this assumes we have at most 256x256 cells
    635         // per workspace screen
    636         if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
    637             // If the horizontal or vertical span is set to -1, it is taken to
    638             // mean that it spans the extent of the CellLayout
    639             if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
    640             if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
    641 
    642             child.setId(childId);
    643 
    644             mShortcutsAndWidgets.addView(child, index, lp);
    645 
    646             if (markCells) markCellsAsOccupiedForView(child);
    647 
    648             return true;
    649         }
    650         return false;
    651     }
    652 
    653     @Override
    654     public void removeAllViews() {
    655         clearOccupiedCells();
    656         mShortcutsAndWidgets.removeAllViews();
    657     }
    658 
    659     @Override
    660     public void removeAllViewsInLayout() {
    661         if (mShortcutsAndWidgets.getChildCount() > 0) {
    662             clearOccupiedCells();
    663             mShortcutsAndWidgets.removeAllViewsInLayout();
    664         }
    665     }
    666 
    667     public void removeViewWithoutMarkingCells(View view) {
    668         mShortcutsAndWidgets.removeView(view);
    669     }
    670 
    671     @Override
    672     public void removeView(View view) {
    673         markCellsAsUnoccupiedForView(view);
    674         mShortcutsAndWidgets.removeView(view);
    675     }
    676 
    677     @Override
    678     public void removeViewAt(int index) {
    679         markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
    680         mShortcutsAndWidgets.removeViewAt(index);
    681     }
    682 
    683     @Override
    684     public void removeViewInLayout(View view) {
    685         markCellsAsUnoccupiedForView(view);
    686         mShortcutsAndWidgets.removeViewInLayout(view);
    687     }
    688 
    689     @Override
    690     public void removeViews(int start, int count) {
    691         for (int i = start; i < start + count; i++) {
    692             markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
    693         }
    694         mShortcutsAndWidgets.removeViews(start, count);
    695     }
    696 
    697     @Override
    698     public void removeViewsInLayout(int start, int count) {
    699         for (int i = start; i < start + count; i++) {
    700             markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
    701         }
    702         mShortcutsAndWidgets.removeViewsInLayout(start, count);
    703     }
    704 
    705     @Override
    706     protected void onAttachedToWindow() {
    707         super.onAttachedToWindow();
    708         mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
    709     }
    710 
    711     public void setTagToCellInfoForPoint(int touchX, int touchY) {
    712         final CellInfo cellInfo = mCellInfo;
    713         Rect frame = mRect;
    714         final int x = touchX + getScrollX();
    715         final int y = touchY + getScrollY();
    716         final int count = mShortcutsAndWidgets.getChildCount();
    717 
    718         boolean found = false;
    719         for (int i = count - 1; i >= 0; i--) {
    720             final View child = mShortcutsAndWidgets.getChildAt(i);
    721             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    722 
    723             if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
    724                     lp.isLockedToGrid) {
    725                 child.getHitRect(frame);
    726 
    727                 float scale = child.getScaleX();
    728                 frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
    729                         child.getBottom());
    730                 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
    731                 // offset that by this CellLayout's padding to test an (x,y) point that is relative
    732                 // to this view.
    733                 frame.offset(getPaddingLeft(), getPaddingTop());
    734                 frame.inset((int) (frame.width() * (1f - scale) / 2),
    735                         (int) (frame.height() * (1f - scale) / 2));
    736 
    737                 if (frame.contains(x, y)) {
    738                     cellInfo.cell = child;
    739                     cellInfo.cellX = lp.cellX;
    740                     cellInfo.cellY = lp.cellY;
    741                     cellInfo.spanX = lp.cellHSpan;
    742                     cellInfo.spanY = lp.cellVSpan;
    743                     found = true;
    744                     break;
    745                 }
    746             }
    747         }
    748 
    749         mLastDownOnOccupiedCell = found;
    750 
    751         if (!found) {
    752             final int cellXY[] = mTmpXY;
    753             pointToCellExact(x, y, cellXY);
    754 
    755             cellInfo.cell = null;
    756             cellInfo.cellX = cellXY[0];
    757             cellInfo.cellY = cellXY[1];
    758             cellInfo.spanX = 1;
    759             cellInfo.spanY = 1;
    760         }
    761         setTag(cellInfo);
    762     }
    763 
    764     @Override
    765     public boolean onInterceptTouchEvent(MotionEvent ev) {
    766         // First we clear the tag to ensure that on every touch down we start with a fresh slate,
    767         // even in the case where we return early. Not clearing here was causing bugs whereby on
    768         // long-press we'd end up picking up an item from a previous drag operation.
    769         final int action = ev.getAction();
    770 
    771         if (action == MotionEvent.ACTION_DOWN) {
    772             clearTagCellInfo();
    773         }
    774 
    775         if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
    776             return true;
    777         }
    778 
    779         if (action == MotionEvent.ACTION_DOWN) {
    780             setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
    781         }
    782 
    783         return false;
    784     }
    785 
    786     private void clearTagCellInfo() {
    787         final CellInfo cellInfo = mCellInfo;
    788         cellInfo.cell = null;
    789         cellInfo.cellX = -1;
    790         cellInfo.cellY = -1;
    791         cellInfo.spanX = 0;
    792         cellInfo.spanY = 0;
    793         setTag(cellInfo);
    794     }
    795 
    796     public CellInfo getTag() {
    797         return (CellInfo) super.getTag();
    798     }
    799 
    800     /**
    801      * Given a point, return the cell that strictly encloses that point
    802      * @param x X coordinate of the point
    803      * @param y Y coordinate of the point
    804      * @param result Array of 2 ints to hold the x and y coordinate of the cell
    805      */
    806     void pointToCellExact(int x, int y, int[] result) {
    807         final int hStartPadding = getPaddingLeft();
    808         final int vStartPadding = getPaddingTop();
    809 
    810         result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
    811         result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
    812 
    813         final int xAxis = mCountX;
    814         final int yAxis = mCountY;
    815 
    816         if (result[0] < 0) result[0] = 0;
    817         if (result[0] >= xAxis) result[0] = xAxis - 1;
    818         if (result[1] < 0) result[1] = 0;
    819         if (result[1] >= yAxis) result[1] = yAxis - 1;
    820     }
    821 
    822     /**
    823      * Given a point, return the cell that most closely encloses that point
    824      * @param x X coordinate of the point
    825      * @param y Y coordinate of the point
    826      * @param result Array of 2 ints to hold the x and y coordinate of the cell
    827      */
    828     void pointToCellRounded(int x, int y, int[] result) {
    829         pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
    830     }
    831 
    832     /**
    833      * Given a cell coordinate, return the point that represents the upper left corner of that cell
    834      *
    835      * @param cellX X coordinate of the cell
    836      * @param cellY Y coordinate of the cell
    837      *
    838      * @param result Array of 2 ints to hold the x and y coordinate of the point
    839      */
    840     void cellToPoint(int cellX, int cellY, int[] result) {
    841         final int hStartPadding = getPaddingLeft();
    842         final int vStartPadding = getPaddingTop();
    843 
    844         result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
    845         result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
    846     }
    847 
    848     /**
    849      * Given a cell coordinate, return the point that represents the center of the cell
    850      *
    851      * @param cellX X coordinate of the cell
    852      * @param cellY Y coordinate of the cell
    853      *
    854      * @param result Array of 2 ints to hold the x and y coordinate of the point
    855      */
    856     void cellToCenterPoint(int cellX, int cellY, int[] result) {
    857         regionToCenterPoint(cellX, cellY, 1, 1, result);
    858     }
    859 
    860     /**
    861      * Given a cell coordinate and span return the point that represents the center of the regio
    862      *
    863      * @param cellX X coordinate of the cell
    864      * @param cellY Y coordinate of the cell
    865      *
    866      * @param result Array of 2 ints to hold the x and y coordinate of the point
    867      */
    868     void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
    869         final int hStartPadding = getPaddingLeft();
    870         final int vStartPadding = getPaddingTop();
    871         result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
    872                 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
    873         result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
    874                 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
    875     }
    876 
    877      /**
    878      * Given a cell coordinate and span fills out a corresponding pixel rect
    879      *
    880      * @param cellX X coordinate of the cell
    881      * @param cellY Y coordinate of the cell
    882      * @param result Rect in which to write the result
    883      */
    884      void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
    885         final int hStartPadding = getPaddingLeft();
    886         final int vStartPadding = getPaddingTop();
    887         final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
    888         final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
    889         result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
    890                 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
    891     }
    892 
    893     public float getDistanceFromCell(float x, float y, int[] cell) {
    894         cellToCenterPoint(cell[0], cell[1], mTmpPoint);
    895         float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
    896                 Math.pow(y - mTmpPoint[1], 2));
    897         return distance;
    898     }
    899 
    900     int getCellWidth() {
    901         return mCellWidth;
    902     }
    903 
    904     int getCellHeight() {
    905         return mCellHeight;
    906     }
    907 
    908     int getWidthGap() {
    909         return mWidthGap;
    910     }
    911 
    912     int getHeightGap() {
    913         return mHeightGap;
    914     }
    915 
    916     Rect getContentRect(Rect r) {
    917         if (r == null) {
    918             r = new Rect();
    919         }
    920         int left = getPaddingLeft();
    921         int top = getPaddingTop();
    922         int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
    923         int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
    924         r.set(left, top, right, bottom);
    925         return r;
    926     }
    927 
    928     static void getMetrics(Rect metrics, Resources res, int measureWidth, int measureHeight,
    929             int countX, int countY, int orientation) {
    930         int numWidthGaps = countX - 1;
    931         int numHeightGaps = countY - 1;
    932 
    933         int widthGap;
    934         int heightGap;
    935         int cellWidth;
    936         int cellHeight;
    937         int paddingLeft;
    938         int paddingRight;
    939         int paddingTop;
    940         int paddingBottom;
    941 
    942         int maxGap = res.getDimensionPixelSize(R.dimen.workspace_max_gap);
    943         if (orientation == LANDSCAPE) {
    944             cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_land);
    945             cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_land);
    946             widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_land);
    947             heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_land);
    948             paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_land);
    949             paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_land);
    950             paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_land);
    951             paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_land);
    952         } else {
    953             // PORTRAIT
    954             cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_port);
    955             cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_port);
    956             widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_port);
    957             heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_port);
    958             paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_port);
    959             paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_port);
    960             paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_port);
    961             paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_port);
    962         }
    963 
    964         if (widthGap < 0 || heightGap < 0) {
    965             int hSpace = measureWidth - paddingLeft - paddingRight;
    966             int vSpace = measureHeight - paddingTop - paddingBottom;
    967             int hFreeSpace = hSpace - (countX * cellWidth);
    968             int vFreeSpace = vSpace - (countY * cellHeight);
    969             widthGap = Math.min(maxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
    970             heightGap = Math.min(maxGap, numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
    971         }
    972         metrics.set(cellWidth, cellHeight, widthGap, heightGap);
    973     }
    974 
    975     @Override
    976     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    977         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    978         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    979 
    980         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    981         int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
    982 
    983         if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
    984             throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
    985         }
    986 
    987         int numWidthGaps = mCountX - 1;
    988         int numHeightGaps = mCountY - 1;
    989 
    990         if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
    991             int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
    992             int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
    993             int hFreeSpace = hSpace - (mCountX * mCellWidth);
    994             int vFreeSpace = vSpace - (mCountY * mCellHeight);
    995             mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
    996             mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
    997             mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
    998                     mCountX);
    999         } else {
   1000             mWidthGap = mOriginalWidthGap;
   1001             mHeightGap = mOriginalHeightGap;
   1002         }
   1003 
   1004         // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
   1005         int newWidth = widthSpecSize;
   1006         int newHeight = heightSpecSize;
   1007         if (widthSpecMode == MeasureSpec.AT_MOST) {
   1008             newWidth = getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
   1009                 ((mCountX - 1) * mWidthGap);
   1010             newHeight = getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
   1011                 ((mCountY - 1) * mHeightGap);
   1012             setMeasuredDimension(newWidth, newHeight);
   1013         }
   1014 
   1015         int count = getChildCount();
   1016         for (int i = 0; i < count; i++) {
   1017             View child = getChildAt(i);
   1018             int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
   1019                     getPaddingRight(), MeasureSpec.EXACTLY);
   1020             int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
   1021                     getPaddingBottom(), MeasureSpec.EXACTLY);
   1022             child.measure(childWidthMeasureSpec, childheightMeasureSpec);
   1023         }
   1024         setMeasuredDimension(newWidth, newHeight);
   1025     }
   1026 
   1027     @Override
   1028     protected void onLayout(boolean changed, int l, int t, int r, int b) {
   1029         int count = getChildCount();
   1030         for (int i = 0; i < count; i++) {
   1031             View child = getChildAt(i);
   1032             child.layout(getPaddingLeft(), getPaddingTop(),
   1033                     r - l - getPaddingRight(), b - t - getPaddingBottom());
   1034         }
   1035     }
   1036 
   1037     @Override
   1038     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   1039         super.onSizeChanged(w, h, oldw, oldh);
   1040         mBackgroundRect.set(0, 0, w, h);
   1041         mForegroundRect.set(mForegroundPadding, mForegroundPadding,
   1042                 w - mForegroundPadding, h - mForegroundPadding);
   1043     }
   1044 
   1045     @Override
   1046     protected void setChildrenDrawingCacheEnabled(boolean enabled) {
   1047         mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
   1048     }
   1049 
   1050     @Override
   1051     protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
   1052         mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
   1053     }
   1054 
   1055     public float getBackgroundAlpha() {
   1056         return mBackgroundAlpha;
   1057     }
   1058 
   1059     public void setBackgroundAlphaMultiplier(float multiplier) {
   1060         if (mBackgroundAlphaMultiplier != multiplier) {
   1061             mBackgroundAlphaMultiplier = multiplier;
   1062             invalidate();
   1063         }
   1064     }
   1065 
   1066     public float getBackgroundAlphaMultiplier() {
   1067         return mBackgroundAlphaMultiplier;
   1068     }
   1069 
   1070     public void setBackgroundAlpha(float alpha) {
   1071         if (mBackgroundAlpha != alpha) {
   1072             mBackgroundAlpha = alpha;
   1073             invalidate();
   1074         }
   1075     }
   1076 
   1077     public void setShortcutAndWidgetAlpha(float alpha) {
   1078         final int childCount = getChildCount();
   1079         for (int i = 0; i < childCount; i++) {
   1080             getChildAt(i).setAlpha(alpha);
   1081         }
   1082     }
   1083 
   1084     public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
   1085         if (getChildCount() > 0) {
   1086             return (ShortcutAndWidgetContainer) getChildAt(0);
   1087         }
   1088         return null;
   1089     }
   1090 
   1091     public View getChildAt(int x, int y) {
   1092         return mShortcutsAndWidgets.getChildAt(x, y);
   1093     }
   1094 
   1095     public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
   1096             int delay, boolean permanent, boolean adjustOccupied) {
   1097         ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
   1098         boolean[][] occupied = mOccupied;
   1099         if (!permanent) {
   1100             occupied = mTmpOccupied;
   1101         }
   1102 
   1103         if (clc.indexOfChild(child) != -1) {
   1104             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
   1105             final ItemInfo info = (ItemInfo) child.getTag();
   1106 
   1107             // We cancel any existing animations
   1108             if (mReorderAnimators.containsKey(lp)) {
   1109                 mReorderAnimators.get(lp).cancel();
   1110                 mReorderAnimators.remove(lp);
   1111             }
   1112 
   1113             final int oldX = lp.x;
   1114             final int oldY = lp.y;
   1115             if (adjustOccupied) {
   1116                 occupied[lp.cellX][lp.cellY] = false;
   1117                 occupied[cellX][cellY] = true;
   1118             }
   1119             lp.isLockedToGrid = true;
   1120             if (permanent) {
   1121                 lp.cellX = info.cellX = cellX;
   1122                 lp.cellY = info.cellY = cellY;
   1123             } else {
   1124                 lp.tmpCellX = cellX;
   1125                 lp.tmpCellY = cellY;
   1126             }
   1127             clc.setupLp(lp);
   1128             lp.isLockedToGrid = false;
   1129             final int newX = lp.x;
   1130             final int newY = lp.y;
   1131 
   1132             lp.x = oldX;
   1133             lp.y = oldY;
   1134 
   1135             // Exit early if we're not actually moving the view
   1136             if (oldX == newX && oldY == newY) {
   1137                 lp.isLockedToGrid = true;
   1138                 return true;
   1139             }
   1140 
   1141             ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
   1142             va.setDuration(duration);
   1143             mReorderAnimators.put(lp, va);
   1144 
   1145             va.addUpdateListener(new AnimatorUpdateListener() {
   1146                 @Override
   1147                 public void onAnimationUpdate(ValueAnimator animation) {
   1148                     float r = ((Float) animation.getAnimatedValue()).floatValue();
   1149                     lp.x = (int) ((1 - r) * oldX + r * newX);
   1150                     lp.y = (int) ((1 - r) * oldY + r * newY);
   1151                     child.requestLayout();
   1152                 }
   1153             });
   1154             va.addListener(new AnimatorListenerAdapter() {
   1155                 boolean cancelled = false;
   1156                 public void onAnimationEnd(Animator animation) {
   1157                     // If the animation was cancelled, it means that another animation
   1158                     // has interrupted this one, and we don't want to lock the item into
   1159                     // place just yet.
   1160                     if (!cancelled) {
   1161                         lp.isLockedToGrid = true;
   1162                         child.requestLayout();
   1163                     }
   1164                     if (mReorderAnimators.containsKey(lp)) {
   1165                         mReorderAnimators.remove(lp);
   1166                     }
   1167                 }
   1168                 public void onAnimationCancel(Animator animation) {
   1169                     cancelled = true;
   1170                 }
   1171             });
   1172             va.setStartDelay(delay);
   1173             va.start();
   1174             return true;
   1175         }
   1176         return false;
   1177     }
   1178 
   1179     /**
   1180      * Estimate where the top left cell of the dragged item will land if it is dropped.
   1181      *
   1182      * @param originX The X value of the top left corner of the item
   1183      * @param originY The Y value of the top left corner of the item
   1184      * @param spanX The number of horizontal cells that the item spans
   1185      * @param spanY The number of vertical cells that the item spans
   1186      * @param result The estimated drop cell X and Y.
   1187      */
   1188     void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
   1189         final int countX = mCountX;
   1190         final int countY = mCountY;
   1191 
   1192         // pointToCellRounded takes the top left of a cell but will pad that with
   1193         // cellWidth/2 and cellHeight/2 when finding the matching cell
   1194         pointToCellRounded(originX, originY, result);
   1195 
   1196         // If the item isn't fully on this screen, snap to the edges
   1197         int rightOverhang = result[0] + spanX - countX;
   1198         if (rightOverhang > 0) {
   1199             result[0] -= rightOverhang; // Snap to right
   1200         }
   1201         result[0] = Math.max(0, result[0]); // Snap to left
   1202         int bottomOverhang = result[1] + spanY - countY;
   1203         if (bottomOverhang > 0) {
   1204             result[1] -= bottomOverhang; // Snap to bottom
   1205         }
   1206         result[1] = Math.max(0, result[1]); // Snap to top
   1207     }
   1208 
   1209     void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
   1210             int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
   1211         final int oldDragCellX = mDragCell[0];
   1212         final int oldDragCellY = mDragCell[1];
   1213 
   1214         if (v != null && dragOffset == null) {
   1215             mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
   1216         } else {
   1217             mDragCenter.set(originX, originY);
   1218         }
   1219 
   1220         if (dragOutline == null && v == null) {
   1221             return;
   1222         }
   1223 
   1224         if (cellX != oldDragCellX || cellY != oldDragCellY) {
   1225             mDragCell[0] = cellX;
   1226             mDragCell[1] = cellY;
   1227             // Find the top left corner of the rect the object will occupy
   1228             final int[] topLeft = mTmpPoint;
   1229             cellToPoint(cellX, cellY, topLeft);
   1230 
   1231             int left = topLeft[0];
   1232             int top = topLeft[1];
   1233 
   1234             if (v != null && dragOffset == null) {
   1235                 // When drawing the drag outline, it did not account for margin offsets
   1236                 // added by the view's parent.
   1237                 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
   1238                 left += lp.leftMargin;
   1239                 top += lp.topMargin;
   1240 
   1241                 // Offsets due to the size difference between the View and the dragOutline.
   1242                 // There is a size difference to account for the outer blur, which may lie
   1243                 // outside the bounds of the view.
   1244                 top += (v.getHeight() - dragOutline.getHeight()) / 2;
   1245                 // We center about the x axis
   1246                 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
   1247                         - dragOutline.getWidth()) / 2;
   1248             } else {
   1249                 if (dragOffset != null && dragRegion != null) {
   1250                     // Center the drag region *horizontally* in the cell and apply a drag
   1251                     // outline offset
   1252                     left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
   1253                              - dragRegion.width()) / 2;
   1254                     top += dragOffset.y;
   1255                 } else {
   1256                     // Center the drag outline in the cell
   1257                     left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
   1258                             - dragOutline.getWidth()) / 2;
   1259                     top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
   1260                             - dragOutline.getHeight()) / 2;
   1261                 }
   1262             }
   1263             final int oldIndex = mDragOutlineCurrent;
   1264             mDragOutlineAnims[oldIndex].animateOut();
   1265             mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
   1266             Rect r = mDragOutlines[mDragOutlineCurrent];
   1267             r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
   1268             if (resize) {
   1269                 cellToRect(cellX, cellY, spanX, spanY, r);
   1270             }
   1271 
   1272             mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
   1273             mDragOutlineAnims[mDragOutlineCurrent].animateIn();
   1274         }
   1275     }
   1276 
   1277     public void clearDragOutlines() {
   1278         final int oldIndex = mDragOutlineCurrent;
   1279         mDragOutlineAnims[oldIndex].animateOut();
   1280         mDragCell[0] = mDragCell[1] = -1;
   1281     }
   1282 
   1283     /**
   1284      * Find a vacant area that will fit the given bounds nearest the requested
   1285      * cell location. Uses Euclidean distance to score multiple vacant areas.
   1286      *
   1287      * @param pixelX The X location at which you want to search for a vacant area.
   1288      * @param pixelY The Y location at which you want to search for a vacant area.
   1289      * @param spanX Horizontal span of the object.
   1290      * @param spanY Vertical span of the object.
   1291      * @param result Array in which to place the result, or null (in which case a new array will
   1292      *        be allocated)
   1293      * @return The X, Y cell of a vacant area that can contain this object,
   1294      *         nearest the requested location.
   1295      */
   1296     int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
   1297             int[] result) {
   1298         return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
   1299     }
   1300 
   1301     /**
   1302      * Find a vacant area that will fit the given bounds nearest the requested
   1303      * cell location. Uses Euclidean distance to score multiple vacant areas.
   1304      *
   1305      * @param pixelX The X location at which you want to search for a vacant area.
   1306      * @param pixelY The Y location at which you want to search for a vacant area.
   1307      * @param minSpanX The minimum horizontal span required
   1308      * @param minSpanY The minimum vertical span required
   1309      * @param spanX Horizontal span of the object.
   1310      * @param spanY Vertical span of the object.
   1311      * @param result Array in which to place the result, or null (in which case a new array will
   1312      *        be allocated)
   1313      * @return The X, Y cell of a vacant area that can contain this object,
   1314      *         nearest the requested location.
   1315      */
   1316     int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
   1317             int spanY, int[] result, int[] resultSpan) {
   1318         return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
   1319                 result, resultSpan);
   1320     }
   1321 
   1322     /**
   1323      * Find a vacant area that will fit the given bounds nearest the requested
   1324      * cell location. Uses Euclidean distance to score multiple vacant areas.
   1325      *
   1326      * @param pixelX The X location at which you want to search for a vacant area.
   1327      * @param pixelY The Y location at which you want to search for a vacant area.
   1328      * @param spanX Horizontal span of the object.
   1329      * @param spanY Vertical span of the object.
   1330      * @param ignoreOccupied If true, the result can be an occupied cell
   1331      * @param result Array in which to place the result, or null (in which case a new array will
   1332      *        be allocated)
   1333      * @return The X, Y cell of a vacant area that can contain this object,
   1334      *         nearest the requested location.
   1335      */
   1336     int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
   1337             boolean ignoreOccupied, int[] result) {
   1338         return findNearestArea(pixelX, pixelY, spanX, spanY,
   1339                 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
   1340     }
   1341 
   1342     private final Stack<Rect> mTempRectStack = new Stack<Rect>();
   1343     private void lazyInitTempRectStack() {
   1344         if (mTempRectStack.isEmpty()) {
   1345             for (int i = 0; i < mCountX * mCountY; i++) {
   1346                 mTempRectStack.push(new Rect());
   1347             }
   1348         }
   1349     }
   1350 
   1351     private void recycleTempRects(Stack<Rect> used) {
   1352         while (!used.isEmpty()) {
   1353             mTempRectStack.push(used.pop());
   1354         }
   1355     }
   1356 
   1357     /**
   1358      * Find a vacant area that will fit the given bounds nearest the requested
   1359      * cell location. Uses Euclidean distance to score multiple vacant areas.
   1360      *
   1361      * @param pixelX The X location at which you want to search for a vacant area.
   1362      * @param pixelY The Y location at which you want to search for a vacant area.
   1363      * @param minSpanX The minimum horizontal span required
   1364      * @param minSpanY The minimum vertical span required
   1365      * @param spanX Horizontal span of the object.
   1366      * @param spanY Vertical span of the object.
   1367      * @param ignoreOccupied If true, the result can be an occupied cell
   1368      * @param result Array in which to place the result, or null (in which case a new array will
   1369      *        be allocated)
   1370      * @return The X, Y cell of a vacant area that can contain this object,
   1371      *         nearest the requested location.
   1372      */
   1373     int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
   1374             View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
   1375             boolean[][] occupied) {
   1376         lazyInitTempRectStack();
   1377         // mark space take by ignoreView as available (method checks if ignoreView is null)
   1378         markCellsAsUnoccupiedForView(ignoreView, occupied);
   1379 
   1380         // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
   1381         // to the center of the item, but we are searching based on the top-left cell, so
   1382         // we translate the point over to correspond to the top-left.
   1383         pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
   1384         pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
   1385 
   1386         // Keep track of best-scoring drop area
   1387         final int[] bestXY = result != null ? result : new int[2];
   1388         double bestDistance = Double.MAX_VALUE;
   1389         final Rect bestRect = new Rect(-1, -1, -1, -1);
   1390         final Stack<Rect> validRegions = new Stack<Rect>();
   1391 
   1392         final int countX = mCountX;
   1393         final int countY = mCountY;
   1394 
   1395         if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
   1396                 spanX < minSpanX || spanY < minSpanY) {
   1397             return bestXY;
   1398         }
   1399 
   1400         for (int y = 0; y < countY - (minSpanY - 1); y++) {
   1401             inner:
   1402             for (int x = 0; x < countX - (minSpanX - 1); x++) {
   1403                 int ySize = -1;
   1404                 int xSize = -1;
   1405                 if (ignoreOccupied) {
   1406                     // First, let's see if this thing fits anywhere
   1407                     for (int i = 0; i < minSpanX; i++) {
   1408                         for (int j = 0; j < minSpanY; j++) {
   1409                             if (occupied[x + i][y + j]) {
   1410                                 continue inner;
   1411                             }
   1412                         }
   1413                     }
   1414                     xSize = minSpanX;
   1415                     ySize = minSpanY;
   1416 
   1417                     // We know that the item will fit at _some_ acceptable size, now let's see
   1418                     // how big we can make it. We'll alternate between incrementing x and y spans
   1419                     // until we hit a limit.
   1420                     boolean incX = true;
   1421                     boolean hitMaxX = xSize >= spanX;
   1422                     boolean hitMaxY = ySize >= spanY;
   1423                     while (!(hitMaxX && hitMaxY)) {
   1424                         if (incX && !hitMaxX) {
   1425                             for (int j = 0; j < ySize; j++) {
   1426                                 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
   1427                                     // We can't move out horizontally
   1428                                     hitMaxX = true;
   1429                                 }
   1430                             }
   1431                             if (!hitMaxX) {
   1432                                 xSize++;
   1433                             }
   1434                         } else if (!hitMaxY) {
   1435                             for (int i = 0; i < xSize; i++) {
   1436                                 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
   1437                                     // We can't move out vertically
   1438                                     hitMaxY = true;
   1439                                 }
   1440                             }
   1441                             if (!hitMaxY) {
   1442                                 ySize++;
   1443                             }
   1444                         }
   1445                         hitMaxX |= xSize >= spanX;
   1446                         hitMaxY |= ySize >= spanY;
   1447                         incX = !incX;
   1448                     }
   1449                     incX = true;
   1450                     hitMaxX = xSize >= spanX;
   1451                     hitMaxY = ySize >= spanY;
   1452                 }
   1453                 final int[] cellXY = mTmpXY;
   1454                 cellToCenterPoint(x, y, cellXY);
   1455 
   1456                 // We verify that the current rect is not a sub-rect of any of our previous
   1457                 // candidates. In this case, the current rect is disqualified in favour of the
   1458                 // containing rect.
   1459                 Rect currentRect = mTempRectStack.pop();
   1460                 currentRect.set(x, y, x + xSize, y + ySize);
   1461                 boolean contained = false;
   1462                 for (Rect r : validRegions) {
   1463                     if (r.contains(currentRect)) {
   1464                         contained = true;
   1465                         break;
   1466                     }
   1467                 }
   1468                 validRegions.push(currentRect);
   1469                 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
   1470                         + Math.pow(cellXY[1] - pixelY, 2));
   1471 
   1472                 if ((distance <= bestDistance && !contained) ||
   1473                         currentRect.contains(bestRect)) {
   1474                     bestDistance = distance;
   1475                     bestXY[0] = x;
   1476                     bestXY[1] = y;
   1477                     if (resultSpan != null) {
   1478                         resultSpan[0] = xSize;
   1479                         resultSpan[1] = ySize;
   1480                     }
   1481                     bestRect.set(currentRect);
   1482                 }
   1483             }
   1484         }
   1485         // re-mark space taken by ignoreView as occupied
   1486         markCellsAsOccupiedForView(ignoreView, occupied);
   1487 
   1488         // Return -1, -1 if no suitable location found
   1489         if (bestDistance == Double.MAX_VALUE) {
   1490             bestXY[0] = -1;
   1491             bestXY[1] = -1;
   1492         }
   1493         recycleTempRects(validRegions);
   1494         return bestXY;
   1495     }
   1496 
   1497      /**
   1498      * Find a vacant area that will fit the given bounds nearest the requested
   1499      * cell location, and will also weigh in a suggested direction vector of the
   1500      * desired location. This method computers distance based on unit grid distances,
   1501      * not pixel distances.
   1502      *
   1503      * @param cellX The X cell nearest to which you want to search for a vacant area.
   1504      * @param cellY The Y cell nearest which you want to search for a vacant area.
   1505      * @param spanX Horizontal span of the object.
   1506      * @param spanY Vertical span of the object.
   1507      * @param direction The favored direction in which the views should move from x, y
   1508      * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
   1509      *        matches exactly. Otherwise we find the best matching direction.
   1510      * @param occoupied The array which represents which cells in the CellLayout are occupied
   1511      * @param blockOccupied The array which represents which cells in the specified block (cellX,
   1512      *        cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
   1513      * @param result Array in which to place the result, or null (in which case a new array will
   1514      *        be allocated)
   1515      * @return The X, Y cell of a vacant area that can contain this object,
   1516      *         nearest the requested location.
   1517      */
   1518     private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
   1519             boolean[][] occupied, boolean blockOccupied[][], int[] result) {
   1520         // Keep track of best-scoring drop area
   1521         final int[] bestXY = result != null ? result : new int[2];
   1522         float bestDistance = Float.MAX_VALUE;
   1523         int bestDirectionScore = Integer.MIN_VALUE;
   1524 
   1525         final int countX = mCountX;
   1526         final int countY = mCountY;
   1527 
   1528         for (int y = 0; y < countY - (spanY - 1); y++) {
   1529             inner:
   1530             for (int x = 0; x < countX - (spanX - 1); x++) {
   1531                 // First, let's see if this thing fits anywhere
   1532                 for (int i = 0; i < spanX; i++) {
   1533                     for (int j = 0; j < spanY; j++) {
   1534                         if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
   1535                             continue inner;
   1536                         }
   1537                     }
   1538                 }
   1539 
   1540                 float distance = (float)
   1541                         Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
   1542                 int[] curDirection = mTmpPoint;
   1543                 computeDirectionVector(x - cellX, y - cellY, curDirection);
   1544                 // The direction score is just the dot product of the two candidate direction
   1545                 // and that passed in.
   1546                 int curDirectionScore = direction[0] * curDirection[0] +
   1547                         direction[1] * curDirection[1];
   1548                 boolean exactDirectionOnly = false;
   1549                 boolean directionMatches = direction[0] == curDirection[0] &&
   1550                         direction[0] == curDirection[0];
   1551                 if ((directionMatches || !exactDirectionOnly) &&
   1552                         Float.compare(distance,  bestDistance) < 0 || (Float.compare(distance,
   1553                         bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
   1554                     bestDistance = distance;
   1555                     bestDirectionScore = curDirectionScore;
   1556                     bestXY[0] = x;
   1557                     bestXY[1] = y;
   1558                 }
   1559             }
   1560         }
   1561 
   1562         // Return -1, -1 if no suitable location found
   1563         if (bestDistance == Float.MAX_VALUE) {
   1564             bestXY[0] = -1;
   1565             bestXY[1] = -1;
   1566         }
   1567         return bestXY;
   1568     }
   1569 
   1570     private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
   1571             int[] direction, ItemConfiguration currentState) {
   1572         CellAndSpan c = currentState.map.get(v);
   1573         boolean success = false;
   1574         markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
   1575         markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
   1576 
   1577         findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
   1578 
   1579         if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
   1580             c.x = mTempLocation[0];
   1581             c.y = mTempLocation[1];
   1582             success = true;
   1583         }
   1584         markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
   1585         return success;
   1586     }
   1587 
   1588     /**
   1589      * This helper class defines a cluster of views. It helps with defining complex edges
   1590      * of the cluster and determining how those edges interact with other views. The edges
   1591      * essentially define a fine-grained boundary around the cluster of views -- like a more
   1592      * precise version of a bounding box.
   1593      */
   1594     private class ViewCluster {
   1595         final static int LEFT = 0;
   1596         final static int TOP = 1;
   1597         final static int RIGHT = 2;
   1598         final static int BOTTOM = 3;
   1599 
   1600         ArrayList<View> views;
   1601         ItemConfiguration config;
   1602         Rect boundingRect = new Rect();
   1603 
   1604         int[] leftEdge = new int[mCountY];
   1605         int[] rightEdge = new int[mCountY];
   1606         int[] topEdge = new int[mCountX];
   1607         int[] bottomEdge = new int[mCountX];
   1608         boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
   1609 
   1610         @SuppressWarnings("unchecked")
   1611         public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
   1612             this.views = (ArrayList<View>) views.clone();
   1613             this.config = config;
   1614             resetEdges();
   1615         }
   1616 
   1617         void resetEdges() {
   1618             for (int i = 0; i < mCountX; i++) {
   1619                 topEdge[i] = -1;
   1620                 bottomEdge[i] = -1;
   1621             }
   1622             for (int i = 0; i < mCountY; i++) {
   1623                 leftEdge[i] = -1;
   1624                 rightEdge[i] = -1;
   1625             }
   1626             leftEdgeDirty = true;
   1627             rightEdgeDirty = true;
   1628             bottomEdgeDirty = true;
   1629             topEdgeDirty = true;
   1630             boundingRectDirty = true;
   1631         }
   1632 
   1633         void computeEdge(int which, int[] edge) {
   1634             int count = views.size();
   1635             for (int i = 0; i < count; i++) {
   1636                 CellAndSpan cs = config.map.get(views.get(i));
   1637                 switch (which) {
   1638                     case LEFT:
   1639                         int left = cs.x;
   1640                         for (int j = cs.y; j < cs.y + cs.spanY; j++) {
   1641                             if (left < edge[j] || edge[j] < 0) {
   1642                                 edge[j] = left;
   1643                             }
   1644                         }
   1645                         break;
   1646                     case RIGHT:
   1647                         int right = cs.x + cs.spanX;
   1648                         for (int j = cs.y; j < cs.y + cs.spanY; j++) {
   1649                             if (right > edge[j]) {
   1650                                 edge[j] = right;
   1651                             }
   1652                         }
   1653                         break;
   1654                     case TOP:
   1655                         int top = cs.y;
   1656                         for (int j = cs.x; j < cs.x + cs.spanX; j++) {
   1657                             if (top < edge[j] || edge[j] < 0) {
   1658                                 edge[j] = top;
   1659                             }
   1660                         }
   1661                         break;
   1662                     case BOTTOM:
   1663                         int bottom = cs.y + cs.spanY;
   1664                         for (int j = cs.x; j < cs.x + cs.spanX; j++) {
   1665                             if (bottom > edge[j]) {
   1666                                 edge[j] = bottom;
   1667                             }
   1668                         }
   1669                         break;
   1670                 }
   1671             }
   1672         }
   1673 
   1674         boolean isViewTouchingEdge(View v, int whichEdge) {
   1675             CellAndSpan cs = config.map.get(v);
   1676 
   1677             int[] edge = getEdge(whichEdge);
   1678 
   1679             switch (whichEdge) {
   1680                 case LEFT:
   1681                     for (int i = cs.y; i < cs.y + cs.spanY; i++) {
   1682                         if (edge[i] == cs.x + cs.spanX) {
   1683                             return true;
   1684                         }
   1685                     }
   1686                     break;
   1687                 case RIGHT:
   1688                     for (int i = cs.y; i < cs.y + cs.spanY; i++) {
   1689                         if (edge[i] == cs.x) {
   1690                             return true;
   1691                         }
   1692                     }
   1693                     break;
   1694                 case TOP:
   1695                     for (int i = cs.x; i < cs.x + cs.spanX; i++) {
   1696                         if (edge[i] == cs.y + cs.spanY) {
   1697                             return true;
   1698                         }
   1699                     }
   1700                     break;
   1701                 case BOTTOM:
   1702                     for (int i = cs.x; i < cs.x + cs.spanX; i++) {
   1703                         if (edge[i] == cs.y) {
   1704                             return true;
   1705                         }
   1706                     }
   1707                     break;
   1708             }
   1709             return false;
   1710         }
   1711 
   1712         void shift(int whichEdge, int delta) {
   1713             for (View v: views) {
   1714                 CellAndSpan c = config.map.get(v);
   1715                 switch (whichEdge) {
   1716                     case LEFT:
   1717                         c.x -= delta;
   1718                         break;
   1719                     case RIGHT:
   1720                         c.x += delta;
   1721                         break;
   1722                     case TOP:
   1723                         c.y -= delta;
   1724                         break;
   1725                     case BOTTOM:
   1726                     default:
   1727                         c.y += delta;
   1728                         break;
   1729                 }
   1730             }
   1731             resetEdges();
   1732         }
   1733 
   1734         public void addView(View v) {
   1735             views.add(v);
   1736             resetEdges();
   1737         }
   1738 
   1739         public Rect getBoundingRect() {
   1740             if (boundingRectDirty) {
   1741                 boolean first = true;
   1742                 for (View v: views) {
   1743                     CellAndSpan c = config.map.get(v);
   1744                     if (first) {
   1745                         boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
   1746                         first = false;
   1747                     } else {
   1748                         boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
   1749                     }
   1750                 }
   1751             }
   1752             return boundingRect;
   1753         }
   1754 
   1755         public int[] getEdge(int which) {
   1756             switch (which) {
   1757                 case LEFT:
   1758                     return getLeftEdge();
   1759                 case RIGHT:
   1760                     return getRightEdge();
   1761                 case TOP:
   1762                     return getTopEdge();
   1763                 case BOTTOM:
   1764                 default:
   1765                     return getBottomEdge();
   1766             }
   1767         }
   1768 
   1769         public int[] getLeftEdge() {
   1770             if (leftEdgeDirty) {
   1771                 computeEdge(LEFT, leftEdge);
   1772             }
   1773             return leftEdge;
   1774         }
   1775 
   1776         public int[] getRightEdge() {
   1777             if (rightEdgeDirty) {
   1778                 computeEdge(RIGHT, rightEdge);
   1779             }
   1780             return rightEdge;
   1781         }
   1782 
   1783         public int[] getTopEdge() {
   1784             if (topEdgeDirty) {
   1785                 computeEdge(TOP, topEdge);
   1786             }
   1787             return topEdge;
   1788         }
   1789 
   1790         public int[] getBottomEdge() {
   1791             if (bottomEdgeDirty) {
   1792                 computeEdge(BOTTOM, bottomEdge);
   1793             }
   1794             return bottomEdge;
   1795         }
   1796 
   1797         PositionComparator comparator = new PositionComparator();
   1798         class PositionComparator implements Comparator<View> {
   1799             int whichEdge = 0;
   1800             public int compare(View left, View right) {
   1801                 CellAndSpan l = config.map.get(left);
   1802                 CellAndSpan r = config.map.get(right);
   1803                 switch (whichEdge) {
   1804                     case LEFT:
   1805                         return (r.x + r.spanX) - (l.x + l.spanX);
   1806                     case RIGHT:
   1807                         return l.x - r.x;
   1808                     case TOP:
   1809                         return (r.y + r.spanY) - (l.y + l.spanY);
   1810                     case BOTTOM:
   1811                     default:
   1812                         return l.y - r.y;
   1813                 }
   1814             }
   1815         }
   1816 
   1817         public void sortConfigurationForEdgePush(int edge) {
   1818             comparator.whichEdge = edge;
   1819             Collections.sort(config.sortedViews, comparator);
   1820         }
   1821     }
   1822 
   1823     private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
   1824             int[] direction, View dragView, ItemConfiguration currentState) {
   1825 
   1826         ViewCluster cluster = new ViewCluster(views, currentState);
   1827         Rect clusterRect = cluster.getBoundingRect();
   1828         int whichEdge;
   1829         int pushDistance;
   1830         boolean fail = false;
   1831 
   1832         // Determine the edge of the cluster that will be leading the push and how far
   1833         // the cluster must be shifted.
   1834         if (direction[0] < 0) {
   1835             whichEdge = ViewCluster.LEFT;
   1836             pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
   1837         } else if (direction[0] > 0) {
   1838             whichEdge = ViewCluster.RIGHT;
   1839             pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
   1840         } else if (direction[1] < 0) {
   1841             whichEdge = ViewCluster.TOP;
   1842             pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
   1843         } else {
   1844             whichEdge = ViewCluster.BOTTOM;
   1845             pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
   1846         }
   1847 
   1848         // Break early for invalid push distance.
   1849         if (pushDistance <= 0) {
   1850             return false;
   1851         }
   1852 
   1853         // Mark the occupied state as false for the group of views we want to move.
   1854         for (View v: views) {
   1855             CellAndSpan c = currentState.map.get(v);
   1856             markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
   1857         }
   1858 
   1859         // We save the current configuration -- if we fail to find a solution we will revert
   1860         // to the initial state. The process of finding a solution modifies the configuration
   1861         // in place, hence the need for revert in the failure case.
   1862         currentState.save();
   1863 
   1864         // The pushing algorithm is simplified by considering the views in the order in which
   1865         // they would be pushed by the cluster. For example, if the cluster is leading with its
   1866         // left edge, we consider sort the views by their right edge, from right to left.
   1867         cluster.sortConfigurationForEdgePush(whichEdge);
   1868 
   1869         while (pushDistance > 0 && !fail) {
   1870             for (View v: currentState.sortedViews) {
   1871                 // For each view that isn't in the cluster, we see if the leading edge of the
   1872                 // cluster is contacting the edge of that view. If so, we add that view to the
   1873                 // cluster.
   1874                 if (!cluster.views.contains(v) && v != dragView) {
   1875                     if (cluster.isViewTouchingEdge(v, whichEdge)) {
   1876                         LayoutParams lp = (LayoutParams) v.getLayoutParams();
   1877                         if (!lp.canReorder) {
   1878                             // The push solution includes the all apps button, this is not viable.
   1879                             fail = true;
   1880                             break;
   1881                         }
   1882                         cluster.addView(v);
   1883                         CellAndSpan c = currentState.map.get(v);
   1884 
   1885                         // Adding view to cluster, mark it as not occupied.
   1886                         markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
   1887                     }
   1888                 }
   1889             }
   1890             pushDistance--;
   1891 
   1892             // The cluster has been completed, now we move the whole thing over in the appropriate
   1893             // direction.
   1894             cluster.shift(whichEdge, 1);
   1895         }
   1896 
   1897         boolean foundSolution = false;
   1898         clusterRect = cluster.getBoundingRect();
   1899 
   1900         // Due to the nature of the algorithm, the only check required to verify a valid solution
   1901         // is to ensure that completed shifted cluster lies completely within the cell layout.
   1902         if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
   1903                 clusterRect.bottom <= mCountY) {
   1904             foundSolution = true;
   1905         } else {
   1906             currentState.restore();
   1907         }
   1908 
   1909         // In either case, we set the occupied array as marked for the location of the views
   1910         for (View v: cluster.views) {
   1911             CellAndSpan c = currentState.map.get(v);
   1912             markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
   1913         }
   1914 
   1915         return foundSolution;
   1916     }
   1917 
   1918     private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
   1919             int[] direction, View dragView, ItemConfiguration currentState) {
   1920         if (views.size() == 0) return true;
   1921 
   1922         boolean success = false;
   1923         Rect boundingRect = null;
   1924         // We construct a rect which represents the entire group of views passed in
   1925         for (View v: views) {
   1926             CellAndSpan c = currentState.map.get(v);
   1927             if (boundingRect == null) {
   1928                 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
   1929             } else {
   1930                 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
   1931             }
   1932         }
   1933 
   1934         // Mark the occupied state as false for the group of views we want to move.
   1935         for (View v: views) {
   1936             CellAndSpan c = currentState.map.get(v);
   1937             markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
   1938         }
   1939 
   1940         boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
   1941         int top = boundingRect.top;
   1942         int left = boundingRect.left;
   1943         // We mark more precisely which parts of the bounding rect are truly occupied, allowing
   1944         // for interlocking.
   1945         for (View v: views) {
   1946             CellAndSpan c = currentState.map.get(v);
   1947             markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
   1948         }
   1949 
   1950         markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
   1951 
   1952         findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
   1953                 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
   1954 
   1955         // If we successfuly found a location by pushing the block of views, we commit it
   1956         if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
   1957             int deltaX = mTempLocation[0] - boundingRect.left;
   1958             int deltaY = mTempLocation[1] - boundingRect.top;
   1959             for (View v: views) {
   1960                 CellAndSpan c = currentState.map.get(v);
   1961                 c.x += deltaX;
   1962                 c.y += deltaY;
   1963             }
   1964             success = true;
   1965         }
   1966 
   1967         // In either case, we set the occupied array as marked for the location of the views
   1968         for (View v: views) {
   1969             CellAndSpan c = currentState.map.get(v);
   1970             markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
   1971         }
   1972         return success;
   1973     }
   1974 
   1975     private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
   1976         markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
   1977     }
   1978 
   1979     // This method tries to find a reordering solution which satisfies the push mechanic by trying
   1980     // to push items in each of the cardinal directions, in an order based on the direction vector
   1981     // passed.
   1982     private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
   1983             int[] direction, View ignoreView, ItemConfiguration solution) {
   1984         if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
   1985             // If the direction vector has two non-zero components, we try pushing
   1986             // separately in each of the components.
   1987             int temp = direction[1];
   1988             direction[1] = 0;
   1989 
   1990             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
   1991                     ignoreView, solution)) {
   1992                 return true;
   1993             }
   1994             direction[1] = temp;
   1995             temp = direction[0];
   1996             direction[0] = 0;
   1997 
   1998             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
   1999                     ignoreView, solution)) {
   2000                 return true;
   2001             }
   2002             // Revert the direction
   2003             direction[0] = temp;
   2004 
   2005             // Now we try pushing in each component of the opposite direction
   2006             direction[0] *= -1;
   2007             direction[1] *= -1;
   2008             temp = direction[1];
   2009             direction[1] = 0;
   2010             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
   2011                     ignoreView, solution)) {
   2012                 return true;
   2013             }
   2014 
   2015             direction[1] = temp;
   2016             temp = direction[0];
   2017             direction[0] = 0;
   2018             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
   2019                     ignoreView, solution)) {
   2020                 return true;
   2021             }
   2022             // revert the direction
   2023             direction[0] = temp;
   2024             direction[0] *= -1;
   2025             direction[1] *= -1;
   2026 
   2027         } else {
   2028             // If the direction vector has a single non-zero component, we push first in the
   2029             // direction of the vector
   2030             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
   2031                     ignoreView, solution)) {
   2032                 return true;
   2033             }
   2034             // Then we try the opposite direction
   2035             direction[0] *= -1;
   2036             direction[1] *= -1;
   2037             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
   2038                     ignoreView, solution)) {
   2039                 return true;
   2040             }
   2041             // Switch the direction back
   2042             direction[0] *= -1;
   2043             direction[1] *= -1;
   2044 
   2045             // If we have failed to find a push solution with the above, then we try
   2046             // to find a solution by pushing along the perpendicular axis.
   2047 
   2048             // Swap the components
   2049             int temp = direction[1];
   2050             direction[1] = direction[0];
   2051             direction[0] = temp;
   2052             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
   2053                     ignoreView, solution)) {
   2054                 return true;
   2055             }
   2056 
   2057             // Then we try the opposite direction
   2058             direction[0] *= -1;
   2059             direction[1] *= -1;
   2060             if (pushViewsToTempLocation(intersectingViews, occupied, direction,
   2061                     ignoreView, solution)) {
   2062                 return true;
   2063             }
   2064             // Switch the direction back
   2065             direction[0] *= -1;
   2066             direction[1] *= -1;
   2067 
   2068             // Swap the components back
   2069             temp = direction[1];
   2070             direction[1] = direction[0];
   2071             direction[0] = temp;
   2072         }
   2073         return false;
   2074     }
   2075 
   2076     private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
   2077             View ignoreView, ItemConfiguration solution) {
   2078         // Return early if get invalid cell positions
   2079         if (cellX < 0 || cellY < 0) return false;
   2080 
   2081         mIntersectingViews.clear();
   2082         mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
   2083 
   2084         // Mark the desired location of the view currently being dragged.
   2085         if (ignoreView != null) {
   2086             CellAndSpan c = solution.map.get(ignoreView);
   2087             if (c != null) {
   2088                 c.x = cellX;
   2089                 c.y = cellY;
   2090             }
   2091         }
   2092         Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
   2093         Rect r1 = new Rect();
   2094         for (View child: solution.map.keySet()) {
   2095             if (child == ignoreView) continue;
   2096             CellAndSpan c = solution.map.get(child);
   2097             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2098             r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
   2099             if (Rect.intersects(r0, r1)) {
   2100                 if (!lp.canReorder) {
   2101                     return false;
   2102                 }
   2103                 mIntersectingViews.add(child);
   2104             }
   2105         }
   2106 
   2107         // First we try to find a solution which respects the push mechanic. That is,
   2108         // we try to find a solution such that no displaced item travels through another item
   2109         // without also displacing that item.
   2110         if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
   2111                 solution)) {
   2112             return true;
   2113         }
   2114 
   2115         // Next we try moving the views as a block, but without requiring the push mechanic.
   2116         if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
   2117                 solution)) {
   2118             return true;
   2119         }
   2120 
   2121         // Ok, they couldn't move as a block, let's move them individually
   2122         for (View v : mIntersectingViews) {
   2123             if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
   2124                 return false;
   2125             }
   2126         }
   2127         return true;
   2128     }
   2129 
   2130     /*
   2131      * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
   2132      * the provided point and the provided cell
   2133      */
   2134     private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
   2135         double angle = Math.atan(((float) deltaY) / deltaX);
   2136 
   2137         result[0] = 0;
   2138         result[1] = 0;
   2139         if (Math.abs(Math.cos(angle)) > 0.5f) {
   2140             result[0] = (int) Math.signum(deltaX);
   2141         }
   2142         if (Math.abs(Math.sin(angle)) > 0.5f) {
   2143             result[1] = (int) Math.signum(deltaY);
   2144         }
   2145     }
   2146 
   2147     private void copyOccupiedArray(boolean[][] occupied) {
   2148         for (int i = 0; i < mCountX; i++) {
   2149             for (int j = 0; j < mCountY; j++) {
   2150                 occupied[i][j] = mOccupied[i][j];
   2151             }
   2152         }
   2153     }
   2154 
   2155     ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
   2156             int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
   2157         // Copy the current state into the solution. This solution will be manipulated as necessary.
   2158         copyCurrentStateToSolution(solution, false);
   2159         // Copy the current occupied array into the temporary occupied array. This array will be
   2160         // manipulated as necessary to find a solution.
   2161         copyOccupiedArray(mTmpOccupied);
   2162 
   2163         // We find the nearest cell into which we would place the dragged item, assuming there's
   2164         // nothing in its way.
   2165         int result[] = new int[2];
   2166         result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
   2167 
   2168         boolean success = false;
   2169         // First we try the exact nearest position of the item being dragged,
   2170         // we will then want to try to move this around to other neighbouring positions
   2171         success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
   2172                 solution);
   2173 
   2174         if (!success) {
   2175             // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
   2176             // x, then 1 in y etc.
   2177             if (spanX > minSpanX && (minSpanY == spanY || decX)) {
   2178                 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
   2179                         dragView, false, solution);
   2180             } else if (spanY > minSpanY) {
   2181                 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
   2182                         dragView, true, solution);
   2183             }
   2184             solution.isSolution = false;
   2185         } else {
   2186             solution.isSolution = true;
   2187             solution.dragViewX = result[0];
   2188             solution.dragViewY = result[1];
   2189             solution.dragViewSpanX = spanX;
   2190             solution.dragViewSpanY = spanY;
   2191         }
   2192         return solution;
   2193     }
   2194 
   2195     private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
   2196         int childCount = mShortcutsAndWidgets.getChildCount();
   2197         for (int i = 0; i < childCount; i++) {
   2198             View child = mShortcutsAndWidgets.getChildAt(i);
   2199             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2200             CellAndSpan c;
   2201             if (temp) {
   2202                 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
   2203             } else {
   2204                 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
   2205             }
   2206             solution.add(child, c);
   2207         }
   2208     }
   2209 
   2210     private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
   2211         for (int i = 0; i < mCountX; i++) {
   2212             for (int j = 0; j < mCountY; j++) {
   2213                 mTmpOccupied[i][j] = false;
   2214             }
   2215         }
   2216 
   2217         int childCount = mShortcutsAndWidgets.getChildCount();
   2218         for (int i = 0; i < childCount; i++) {
   2219             View child = mShortcutsAndWidgets.getChildAt(i);
   2220             if (child == dragView) continue;
   2221             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2222             CellAndSpan c = solution.map.get(child);
   2223             if (c != null) {
   2224                 lp.tmpCellX = c.x;
   2225                 lp.tmpCellY = c.y;
   2226                 lp.cellHSpan = c.spanX;
   2227                 lp.cellVSpan = c.spanY;
   2228                 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
   2229             }
   2230         }
   2231         markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
   2232                 solution.dragViewSpanY, mTmpOccupied, true);
   2233     }
   2234 
   2235     private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
   2236             commitDragView) {
   2237 
   2238         boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
   2239         for (int i = 0; i < mCountX; i++) {
   2240             for (int j = 0; j < mCountY; j++) {
   2241                 occupied[i][j] = false;
   2242             }
   2243         }
   2244 
   2245         int childCount = mShortcutsAndWidgets.getChildCount();
   2246         for (int i = 0; i < childCount; i++) {
   2247             View child = mShortcutsAndWidgets.getChildAt(i);
   2248             if (child == dragView) continue;
   2249             CellAndSpan c = solution.map.get(child);
   2250             if (c != null) {
   2251                 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
   2252                         DESTRUCTIVE_REORDER, false);
   2253                 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
   2254             }
   2255         }
   2256         if (commitDragView) {
   2257             markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
   2258                     solution.dragViewSpanY, occupied, true);
   2259         }
   2260     }
   2261 
   2262     // This method starts or changes the reorder hint animations
   2263     private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
   2264         int childCount = mShortcutsAndWidgets.getChildCount();
   2265         for (int i = 0; i < childCount; i++) {
   2266             View child = mShortcutsAndWidgets.getChildAt(i);
   2267             if (child == dragView) continue;
   2268             CellAndSpan c = solution.map.get(child);
   2269             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2270             if (c != null) {
   2271                 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
   2272                         c.x, c.y, c.spanX, c.spanY);
   2273                 rha.animate();
   2274             }
   2275         }
   2276     }
   2277 
   2278     // Class which represents the reorder hint animations. These animations show that an item is
   2279     // in a temporary state, and hint at where the item will return to.
   2280     class ReorderHintAnimation {
   2281         View child;
   2282         float finalDeltaX;
   2283         float finalDeltaY;
   2284         float initDeltaX;
   2285         float initDeltaY;
   2286         float finalScale;
   2287         float initScale;
   2288         private static final int DURATION = 300;
   2289         Animator a;
   2290 
   2291         public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
   2292                 int spanX, int spanY) {
   2293             regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
   2294             final int x0 = mTmpPoint[0];
   2295             final int y0 = mTmpPoint[1];
   2296             regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
   2297             final int x1 = mTmpPoint[0];
   2298             final int y1 = mTmpPoint[1];
   2299             final int dX = x1 - x0;
   2300             final int dY = y1 - y0;
   2301             finalDeltaX = 0;
   2302             finalDeltaY = 0;
   2303             if (dX == dY && dX == 0) {
   2304             } else {
   2305                 if (dY == 0) {
   2306                     finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
   2307                 } else if (dX == 0) {
   2308                     finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
   2309                 } else {
   2310                     double angle = Math.atan( (float) (dY) / dX);
   2311                     finalDeltaX = (int) (- Math.signum(dX) *
   2312                             Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
   2313                     finalDeltaY = (int) (- Math.signum(dY) *
   2314                             Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
   2315                 }
   2316             }
   2317             initDeltaX = child.getTranslationX();
   2318             initDeltaY = child.getTranslationY();
   2319             finalScale = getChildrenScale() - 4.0f / child.getWidth();
   2320             initScale = child.getScaleX();
   2321             this.child = child;
   2322         }
   2323 
   2324         void animate() {
   2325             if (mShakeAnimators.containsKey(child)) {
   2326                 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
   2327                 oldAnimation.cancel();
   2328                 mShakeAnimators.remove(child);
   2329                 if (finalDeltaX == 0 && finalDeltaY == 0) {
   2330                     completeAnimationImmediately();
   2331                     return;
   2332                 }
   2333             }
   2334             if (finalDeltaX == 0 && finalDeltaY == 0) {
   2335                 return;
   2336             }
   2337             ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
   2338             a = va;
   2339             va.setRepeatMode(ValueAnimator.REVERSE);
   2340             va.setRepeatCount(ValueAnimator.INFINITE);
   2341             va.setDuration(DURATION);
   2342             va.setStartDelay((int) (Math.random() * 60));
   2343             va.addUpdateListener(new AnimatorUpdateListener() {
   2344                 @Override
   2345                 public void onAnimationUpdate(ValueAnimator animation) {
   2346                     float r = ((Float) animation.getAnimatedValue()).floatValue();
   2347                     float x = r * finalDeltaX + (1 - r) * initDeltaX;
   2348                     float y = r * finalDeltaY + (1 - r) * initDeltaY;
   2349                     child.setTranslationX(x);
   2350                     child.setTranslationY(y);
   2351                     float s = r * finalScale + (1 - r) * initScale;
   2352                     child.setScaleX(s);
   2353                     child.setScaleY(s);
   2354                 }
   2355             });
   2356             va.addListener(new AnimatorListenerAdapter() {
   2357                 public void onAnimationRepeat(Animator animation) {
   2358                     // We make sure to end only after a full period
   2359                     initDeltaX = 0;
   2360                     initDeltaY = 0;
   2361                     initScale = getChildrenScale();
   2362                 }
   2363             });
   2364             mShakeAnimators.put(child, this);
   2365             va.start();
   2366         }
   2367 
   2368         private void cancel() {
   2369             if (a != null) {
   2370                 a.cancel();
   2371             }
   2372         }
   2373 
   2374         private void completeAnimationImmediately() {
   2375             if (a != null) {
   2376                 a.cancel();
   2377             }
   2378 
   2379             AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
   2380             a = s;
   2381             s.playTogether(
   2382                 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
   2383                 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
   2384                 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
   2385                 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
   2386             );
   2387             s.setDuration(REORDER_ANIMATION_DURATION);
   2388             s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
   2389             s.start();
   2390         }
   2391     }
   2392 
   2393     private void completeAndClearReorderHintAnimations() {
   2394         for (ReorderHintAnimation a: mShakeAnimators.values()) {
   2395             a.completeAnimationImmediately();
   2396         }
   2397         mShakeAnimators.clear();
   2398     }
   2399 
   2400     private void commitTempPlacement() {
   2401         for (int i = 0; i < mCountX; i++) {
   2402             for (int j = 0; j < mCountY; j++) {
   2403                 mOccupied[i][j] = mTmpOccupied[i][j];
   2404             }
   2405         }
   2406         int childCount = mShortcutsAndWidgets.getChildCount();
   2407         for (int i = 0; i < childCount; i++) {
   2408             View child = mShortcutsAndWidgets.getChildAt(i);
   2409             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2410             ItemInfo info = (ItemInfo) child.getTag();
   2411             // We do a null check here because the item info can be null in the case of the
   2412             // AllApps button in the hotseat.
   2413             if (info != null) {
   2414                 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
   2415                         info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
   2416                     info.requiresDbUpdate = true;
   2417                 }
   2418                 info.cellX = lp.cellX = lp.tmpCellX;
   2419                 info.cellY = lp.cellY = lp.tmpCellY;
   2420                 info.spanX = lp.cellHSpan;
   2421                 info.spanY = lp.cellVSpan;
   2422             }
   2423         }
   2424         mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
   2425     }
   2426 
   2427     public void setUseTempCoords(boolean useTempCoords) {
   2428         int childCount = mShortcutsAndWidgets.getChildCount();
   2429         for (int i = 0; i < childCount; i++) {
   2430             LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
   2431             lp.useTmpCoords = useTempCoords;
   2432         }
   2433     }
   2434 
   2435     ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
   2436             int spanX, int spanY, View dragView, ItemConfiguration solution) {
   2437         int[] result = new int[2];
   2438         int[] resultSpan = new int[2];
   2439         findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
   2440                 resultSpan);
   2441         if (result[0] >= 0 && result[1] >= 0) {
   2442             copyCurrentStateToSolution(solution, false);
   2443             solution.dragViewX = result[0];
   2444             solution.dragViewY = result[1];
   2445             solution.dragViewSpanX = resultSpan[0];
   2446             solution.dragViewSpanY = resultSpan[1];
   2447             solution.isSolution = true;
   2448         } else {
   2449             solution.isSolution = false;
   2450         }
   2451         return solution;
   2452     }
   2453 
   2454     public void prepareChildForDrag(View child) {
   2455         markCellsAsUnoccupiedForView(child);
   2456     }
   2457 
   2458     /* This seems like it should be obvious and straight-forward, but when the direction vector
   2459     needs to match with the notion of the dragView pushing other views, we have to employ
   2460     a slightly more subtle notion of the direction vector. The question is what two points is
   2461     the vector between? The center of the dragView and its desired destination? Not quite, as
   2462     this doesn't necessarily coincide with the interaction of the dragView and items occupying
   2463     those cells. Instead we use some heuristics to often lock the vector to up, down, left
   2464     or right, which helps make pushing feel right.
   2465     */
   2466     private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
   2467             int spanY, View dragView, int[] resultDirection) {
   2468         int[] targetDestination = new int[2];
   2469 
   2470         findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
   2471         Rect dragRect = new Rect();
   2472         regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
   2473         dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
   2474 
   2475         Rect dropRegionRect = new Rect();
   2476         getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
   2477                 dragView, dropRegionRect, mIntersectingViews);
   2478 
   2479         int dropRegionSpanX = dropRegionRect.width();
   2480         int dropRegionSpanY = dropRegionRect.height();
   2481 
   2482         regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
   2483                 dropRegionRect.height(), dropRegionRect);
   2484 
   2485         int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
   2486         int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
   2487 
   2488         if (dropRegionSpanX == mCountX || spanX == mCountX) {
   2489             deltaX = 0;
   2490         }
   2491         if (dropRegionSpanY == mCountY || spanY == mCountY) {
   2492             deltaY = 0;
   2493         }
   2494 
   2495         if (deltaX == 0 && deltaY == 0) {
   2496             // No idea what to do, give a random direction.
   2497             resultDirection[0] = 1;
   2498             resultDirection[1] = 0;
   2499         } else {
   2500             computeDirectionVector(deltaX, deltaY, resultDirection);
   2501         }
   2502     }
   2503 
   2504     // For a given cell and span, fetch the set of views intersecting the region.
   2505     private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
   2506             View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
   2507         if (boundingRect != null) {
   2508             boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
   2509         }
   2510         intersectingViews.clear();
   2511         Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
   2512         Rect r1 = new Rect();
   2513         final int count = mShortcutsAndWidgets.getChildCount();
   2514         for (int i = 0; i < count; i++) {
   2515             View child = mShortcutsAndWidgets.getChildAt(i);
   2516             if (child == dragView) continue;
   2517             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2518             r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
   2519             if (Rect.intersects(r0, r1)) {
   2520                 mIntersectingViews.add(child);
   2521                 if (boundingRect != null) {
   2522                     boundingRect.union(r1);
   2523                 }
   2524             }
   2525         }
   2526     }
   2527 
   2528     boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
   2529             View dragView, int[] result) {
   2530         result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
   2531         getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
   2532                 mIntersectingViews);
   2533         return !mIntersectingViews.isEmpty();
   2534     }
   2535 
   2536     void revertTempState() {
   2537         if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
   2538         final int count = mShortcutsAndWidgets.getChildCount();
   2539         for (int i = 0; i < count; i++) {
   2540             View child = mShortcutsAndWidgets.getChildAt(i);
   2541             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2542             if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
   2543                 lp.tmpCellX = lp.cellX;
   2544                 lp.tmpCellY = lp.cellY;
   2545                 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
   2546                         0, false, false);
   2547             }
   2548         }
   2549         completeAndClearReorderHintAnimations();
   2550         setItemPlacementDirty(false);
   2551     }
   2552 
   2553     boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
   2554             View dragView, int[] direction, boolean commit) {
   2555         int[] pixelXY = new int[2];
   2556         regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
   2557 
   2558         // First we determine if things have moved enough to cause a different layout
   2559         ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
   2560                  spanX,  spanY, direction, dragView,  true,  new ItemConfiguration());
   2561 
   2562         setUseTempCoords(true);
   2563         if (swapSolution != null && swapSolution.isSolution) {
   2564             // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
   2565             // committing anything or animating anything as we just want to determine if a solution
   2566             // exists
   2567             copySolutionToTempState(swapSolution, dragView);
   2568             setItemPlacementDirty(true);
   2569             animateItemsToSolution(swapSolution, dragView, commit);
   2570 
   2571             if (commit) {
   2572                 commitTempPlacement();
   2573                 completeAndClearReorderHintAnimations();
   2574                 setItemPlacementDirty(false);
   2575             } else {
   2576                 beginOrAdjustHintAnimations(swapSolution, dragView,
   2577                         REORDER_ANIMATION_DURATION);
   2578             }
   2579             mShortcutsAndWidgets.requestLayout();
   2580         }
   2581         return swapSolution.isSolution;
   2582     }
   2583 
   2584     int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
   2585             View dragView, int[] result, int resultSpan[], int mode) {
   2586         // First we determine if things have moved enough to cause a different layout
   2587         result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
   2588 
   2589         if (resultSpan == null) {
   2590             resultSpan = new int[2];
   2591         }
   2592 
   2593         // When we are checking drop validity or actually dropping, we don't recompute the
   2594         // direction vector, since we want the solution to match the preview, and it's possible
   2595         // that the exact position of the item has changed to result in a new reordering outcome.
   2596         if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
   2597                && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
   2598             mDirectionVector[0] = mPreviousReorderDirection[0];
   2599             mDirectionVector[1] = mPreviousReorderDirection[1];
   2600             // We reset this vector after drop
   2601             if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
   2602                 mPreviousReorderDirection[0] = INVALID_DIRECTION;
   2603                 mPreviousReorderDirection[1] = INVALID_DIRECTION;
   2604             }
   2605         } else {
   2606             getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
   2607             mPreviousReorderDirection[0] = mDirectionVector[0];
   2608             mPreviousReorderDirection[1] = mDirectionVector[1];
   2609         }
   2610 
   2611         ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
   2612                  spanX,  spanY, mDirectionVector, dragView,  true,  new ItemConfiguration());
   2613 
   2614         // We attempt the approach which doesn't shuffle views at all
   2615         ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
   2616                 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
   2617 
   2618         ItemConfiguration finalSolution = null;
   2619         if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
   2620             finalSolution = swapSolution;
   2621         } else if (noShuffleSolution.isSolution) {
   2622             finalSolution = noShuffleSolution;
   2623         }
   2624 
   2625         boolean foundSolution = true;
   2626         if (!DESTRUCTIVE_REORDER) {
   2627             setUseTempCoords(true);
   2628         }
   2629 
   2630         if (finalSolution != null) {
   2631             result[0] = finalSolution.dragViewX;
   2632             result[1] = finalSolution.dragViewY;
   2633             resultSpan[0] = finalSolution.dragViewSpanX;
   2634             resultSpan[1] = finalSolution.dragViewSpanY;
   2635 
   2636             // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
   2637             // committing anything or animating anything as we just want to determine if a solution
   2638             // exists
   2639             if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
   2640                 if (!DESTRUCTIVE_REORDER) {
   2641                     copySolutionToTempState(finalSolution, dragView);
   2642                 }
   2643                 setItemPlacementDirty(true);
   2644                 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
   2645 
   2646                 if (!DESTRUCTIVE_REORDER &&
   2647                         (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
   2648                     commitTempPlacement();
   2649                     completeAndClearReorderHintAnimations();
   2650                     setItemPlacementDirty(false);
   2651                 } else {
   2652                     beginOrAdjustHintAnimations(finalSolution, dragView,
   2653                             REORDER_ANIMATION_DURATION);
   2654                 }
   2655             }
   2656         } else {
   2657             foundSolution = false;
   2658             result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
   2659         }
   2660 
   2661         if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
   2662             setUseTempCoords(false);
   2663         }
   2664 
   2665         mShortcutsAndWidgets.requestLayout();
   2666         return result;
   2667     }
   2668 
   2669     void setItemPlacementDirty(boolean dirty) {
   2670         mItemPlacementDirty = dirty;
   2671     }
   2672     boolean isItemPlacementDirty() {
   2673         return mItemPlacementDirty;
   2674     }
   2675 
   2676     private class ItemConfiguration {
   2677         HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
   2678         private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
   2679         ArrayList<View> sortedViews = new ArrayList<View>();
   2680         boolean isSolution = false;
   2681         int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
   2682 
   2683         void save() {
   2684             // Copy current state into savedMap
   2685             for (View v: map.keySet()) {
   2686                 map.get(v).copy(savedMap.get(v));
   2687             }
   2688         }
   2689 
   2690         void restore() {
   2691             // Restore current state from savedMap
   2692             for (View v: savedMap.keySet()) {
   2693                 savedMap.get(v).copy(map.get(v));
   2694             }
   2695         }
   2696 
   2697         void add(View v, CellAndSpan cs) {
   2698             map.put(v, cs);
   2699             savedMap.put(v, new CellAndSpan());
   2700             sortedViews.add(v);
   2701         }
   2702 
   2703         int area() {
   2704             return dragViewSpanX * dragViewSpanY;
   2705         }
   2706     }
   2707 
   2708     private class CellAndSpan {
   2709         int x, y;
   2710         int spanX, spanY;
   2711 
   2712         public CellAndSpan() {
   2713         }
   2714 
   2715         public void copy(CellAndSpan copy) {
   2716             copy.x = x;
   2717             copy.y = y;
   2718             copy.spanX = spanX;
   2719             copy.spanY = spanY;
   2720         }
   2721 
   2722         public CellAndSpan(int x, int y, int spanX, int spanY) {
   2723             this.x = x;
   2724             this.y = y;
   2725             this.spanX = spanX;
   2726             this.spanY = spanY;
   2727         }
   2728 
   2729         public String toString() {
   2730             return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
   2731         }
   2732 
   2733     }
   2734 
   2735     /**
   2736      * Find a vacant area that will fit the given bounds nearest the requested
   2737      * cell location. Uses Euclidean distance to score multiple vacant areas.
   2738      *
   2739      * @param pixelX The X location at which you want to search for a vacant area.
   2740      * @param pixelY The Y location at which you want to search for a vacant area.
   2741      * @param spanX Horizontal span of the object.
   2742      * @param spanY Vertical span of the object.
   2743      * @param ignoreView Considers space occupied by this view as unoccupied
   2744      * @param result Previously returned value to possibly recycle.
   2745      * @return The X, Y cell of a vacant area that can contain this object,
   2746      *         nearest the requested location.
   2747      */
   2748     int[] findNearestVacantArea(
   2749             int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
   2750         return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
   2751     }
   2752 
   2753     /**
   2754      * Find a vacant area that will fit the given bounds nearest the requested
   2755      * cell location. Uses Euclidean distance to score multiple vacant areas.
   2756      *
   2757      * @param pixelX The X location at which you want to search for a vacant area.
   2758      * @param pixelY The Y location at which you want to search for a vacant area.
   2759      * @param minSpanX The minimum horizontal span required
   2760      * @param minSpanY The minimum vertical span required
   2761      * @param spanX Horizontal span of the object.
   2762      * @param spanY Vertical span of the object.
   2763      * @param ignoreView Considers space occupied by this view as unoccupied
   2764      * @param result Previously returned value to possibly recycle.
   2765      * @return The X, Y cell of a vacant area that can contain this object,
   2766      *         nearest the requested location.
   2767      */
   2768     int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
   2769             int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
   2770         return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
   2771                 result, resultSpan, mOccupied);
   2772     }
   2773 
   2774     /**
   2775      * Find a starting cell position that will fit the given bounds nearest the requested
   2776      * cell location. Uses Euclidean distance to score multiple vacant areas.
   2777      *
   2778      * @param pixelX The X location at which you want to search for a vacant area.
   2779      * @param pixelY The Y location at which you want to search for a vacant area.
   2780      * @param spanX Horizontal span of the object.
   2781      * @param spanY Vertical span of the object.
   2782      * @param ignoreView Considers space occupied by this view as unoccupied
   2783      * @param result Previously returned value to possibly recycle.
   2784      * @return The X, Y cell of a vacant area that can contain this object,
   2785      *         nearest the requested location.
   2786      */
   2787     int[] findNearestArea(
   2788             int pixelX, int pixelY, int spanX, int spanY, int[] result) {
   2789         return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
   2790     }
   2791 
   2792     boolean existsEmptyCell() {
   2793         return findCellForSpan(null, 1, 1);
   2794     }
   2795 
   2796     /**
   2797      * Finds the upper-left coordinate of the first rectangle in the grid that can
   2798      * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
   2799      * then this method will only return coordinates for rectangles that contain the cell
   2800      * (intersectX, intersectY)
   2801      *
   2802      * @param cellXY The array that will contain the position of a vacant cell if such a cell
   2803      *               can be found.
   2804      * @param spanX The horizontal span of the cell we want to find.
   2805      * @param spanY The vertical span of the cell we want to find.
   2806      *
   2807      * @return True if a vacant cell of the specified dimension was found, false otherwise.
   2808      */
   2809     boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
   2810         return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
   2811     }
   2812 
   2813     /**
   2814      * Like above, but ignores any cells occupied by the item "ignoreView"
   2815      *
   2816      * @param cellXY The array that will contain the position of a vacant cell if such a cell
   2817      *               can be found.
   2818      * @param spanX The horizontal span of the cell we want to find.
   2819      * @param spanY The vertical span of the cell we want to find.
   2820      * @param ignoreView The home screen item we should treat as not occupying any space
   2821      * @return
   2822      */
   2823     boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
   2824         return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
   2825                 ignoreView, mOccupied);
   2826     }
   2827 
   2828     /**
   2829      * Like above, but if intersectX and intersectY are not -1, then this method will try to
   2830      * return coordinates for rectangles that contain the cell [intersectX, intersectY]
   2831      *
   2832      * @param spanX The horizontal span of the cell we want to find.
   2833      * @param spanY The vertical span of the cell we want to find.
   2834      * @param ignoreView The home screen item we should treat as not occupying any space
   2835      * @param intersectX The X coordinate of the cell that we should try to overlap
   2836      * @param intersectX The Y coordinate of the cell that we should try to overlap
   2837      *
   2838      * @return True if a vacant cell of the specified dimension was found, false otherwise.
   2839      */
   2840     boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
   2841             int intersectX, int intersectY) {
   2842         return findCellForSpanThatIntersectsIgnoring(
   2843                 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
   2844     }
   2845 
   2846     /**
   2847      * The superset of the above two methods
   2848      */
   2849     boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
   2850             int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
   2851         // mark space take by ignoreView as available (method checks if ignoreView is null)
   2852         markCellsAsUnoccupiedForView(ignoreView, occupied);
   2853 
   2854         boolean foundCell = false;
   2855         while (true) {
   2856             int startX = 0;
   2857             if (intersectX >= 0) {
   2858                 startX = Math.max(startX, intersectX - (spanX - 1));
   2859             }
   2860             int endX = mCountX - (spanX - 1);
   2861             if (intersectX >= 0) {
   2862                 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
   2863             }
   2864             int startY = 0;
   2865             if (intersectY >= 0) {
   2866                 startY = Math.max(startY, intersectY - (spanY - 1));
   2867             }
   2868             int endY = mCountY - (spanY - 1);
   2869             if (intersectY >= 0) {
   2870                 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
   2871             }
   2872 
   2873             for (int y = startY; y < endY && !foundCell; y++) {
   2874                 inner:
   2875                 for (int x = startX; x < endX; x++) {
   2876                     for (int i = 0; i < spanX; i++) {
   2877                         for (int j = 0; j < spanY; j++) {
   2878                             if (occupied[x + i][y + j]) {
   2879                                 // small optimization: we can skip to after the column we just found
   2880                                 // an occupied cell
   2881                                 x += i;
   2882                                 continue inner;
   2883                             }
   2884                         }
   2885                     }
   2886                     if (cellXY != null) {
   2887                         cellXY[0] = x;
   2888                         cellXY[1] = y;
   2889                     }
   2890                     foundCell = true;
   2891                     break;
   2892                 }
   2893             }
   2894             if (intersectX == -1 && intersectY == -1) {
   2895                 break;
   2896             } else {
   2897                 // if we failed to find anything, try again but without any requirements of
   2898                 // intersecting
   2899                 intersectX = -1;
   2900                 intersectY = -1;
   2901                 continue;
   2902             }
   2903         }
   2904 
   2905         // re-mark space taken by ignoreView as occupied
   2906         markCellsAsOccupiedForView(ignoreView, occupied);
   2907         return foundCell;
   2908     }
   2909 
   2910     /**
   2911      * A drag event has begun over this layout.
   2912      * It may have begun over this layout (in which case onDragChild is called first),
   2913      * or it may have begun on another layout.
   2914      */
   2915     void onDragEnter() {
   2916         mDragEnforcer.onDragEnter();
   2917         mDragging = true;
   2918     }
   2919 
   2920     /**
   2921      * Called when drag has left this CellLayout or has been completed (successfully or not)
   2922      */
   2923     void onDragExit() {
   2924         mDragEnforcer.onDragExit();
   2925         // This can actually be called when we aren't in a drag, e.g. when adding a new
   2926         // item to this layout via the customize drawer.
   2927         // Guard against that case.
   2928         if (mDragging) {
   2929             mDragging = false;
   2930         }
   2931 
   2932         // Invalidate the drag data
   2933         mDragCell[0] = mDragCell[1] = -1;
   2934         mDragOutlineAnims[mDragOutlineCurrent].animateOut();
   2935         mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
   2936         revertTempState();
   2937         setIsDragOverlapping(false);
   2938     }
   2939 
   2940     /**
   2941      * Mark a child as having been dropped.
   2942      * At the beginning of the drag operation, the child may have been on another
   2943      * screen, but it is re-parented before this method is called.
   2944      *
   2945      * @param child The child that is being dropped
   2946      */
   2947     void onDropChild(View child) {
   2948         if (child != null) {
   2949             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   2950             lp.dropped = true;
   2951             child.requestLayout();
   2952         }
   2953     }
   2954 
   2955     /**
   2956      * Computes a bounding rectangle for a range of cells
   2957      *
   2958      * @param cellX X coordinate of upper left corner expressed as a cell position
   2959      * @param cellY Y coordinate of upper left corner expressed as a cell position
   2960      * @param cellHSpan Width in cells
   2961      * @param cellVSpan Height in cells
   2962      * @param resultRect Rect into which to put the results
   2963      */
   2964     public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
   2965         final int cellWidth = mCellWidth;
   2966         final int cellHeight = mCellHeight;
   2967         final int widthGap = mWidthGap;
   2968         final int heightGap = mHeightGap;
   2969 
   2970         final int hStartPadding = getPaddingLeft();
   2971         final int vStartPadding = getPaddingTop();
   2972 
   2973         int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
   2974         int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
   2975 
   2976         int x = hStartPadding + cellX * (cellWidth + widthGap);
   2977         int y = vStartPadding + cellY * (cellHeight + heightGap);
   2978 
   2979         resultRect.set(x, y, x + width, y + height);
   2980     }
   2981 
   2982     /**
   2983      * Computes the required horizontal and vertical cell spans to always
   2984      * fit the given rectangle.
   2985      *
   2986      * @param width Width in pixels
   2987      * @param height Height in pixels
   2988      * @param result An array of length 2 in which to store the result (may be null).
   2989      */
   2990     public int[] rectToCell(int width, int height, int[] result) {
   2991         return rectToCell(getResources(), width, height, result);
   2992     }
   2993 
   2994     public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
   2995         // Always assume we're working with the smallest span to make sure we
   2996         // reserve enough space in both orientations.
   2997         int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
   2998         int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
   2999         int smallerSize = Math.min(actualWidth, actualHeight);
   3000 
   3001         // Always round up to next largest cell
   3002         int spanX = (int) Math.ceil(width / (float) smallerSize);
   3003         int spanY = (int) Math.ceil(height / (float) smallerSize);
   3004 
   3005         if (result == null) {
   3006             return new int[] { spanX, spanY };
   3007         }
   3008         result[0] = spanX;
   3009         result[1] = spanY;
   3010         return result;
   3011     }
   3012 
   3013     public int[] cellSpansToSize(int hSpans, int vSpans) {
   3014         int[] size = new int[2];
   3015         size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
   3016         size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
   3017         return size;
   3018     }
   3019 
   3020     /**
   3021      * Calculate the grid spans needed to fit given item
   3022      */
   3023     public void calculateSpans(ItemInfo info) {
   3024         final int minWidth;
   3025         final int minHeight;
   3026 
   3027         if (info instanceof LauncherAppWidgetInfo) {
   3028             minWidth = ((LauncherAppWidgetInfo) info).minWidth;
   3029             minHeight = ((LauncherAppWidgetInfo) info).minHeight;
   3030         } else if (info instanceof PendingAddWidgetInfo) {
   3031             minWidth = ((PendingAddWidgetInfo) info).info.minWidth;
   3032             minHeight = ((PendingAddWidgetInfo) info).info.minHeight;
   3033         } else {
   3034             // It's not a widget, so it must be 1x1
   3035             info.spanX = info.spanY = 1;
   3036             return;
   3037         }
   3038         int[] spans = rectToCell(minWidth, minHeight, null);
   3039         info.spanX = spans[0];
   3040         info.spanY = spans[1];
   3041     }
   3042 
   3043     /**
   3044      * Find the first vacant cell, if there is one.
   3045      *
   3046      * @param vacant Holds the x and y coordinate of the vacant cell
   3047      * @param spanX Horizontal cell span.
   3048      * @param spanY Vertical cell span.
   3049      *
   3050      * @return True if a vacant cell was found
   3051      */
   3052     public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
   3053 
   3054         return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
   3055     }
   3056 
   3057     static boolean findVacantCell(int[] vacant, int spanX, int spanY,
   3058             int xCount, int yCount, boolean[][] occupied) {
   3059 
   3060         for (int y = 0; y < yCount; y++) {
   3061             for (int x = 0; x < xCount; x++) {
   3062                 boolean available = !occupied[x][y];
   3063 out:            for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
   3064                     for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
   3065                         available = available && !occupied[i][j];
   3066                         if (!available) break out;
   3067                     }
   3068                 }
   3069 
   3070                 if (available) {
   3071                     vacant[0] = x;
   3072                     vacant[1] = y;
   3073                     return true;
   3074                 }
   3075             }
   3076         }
   3077 
   3078         return false;
   3079     }
   3080 
   3081     private void clearOccupiedCells() {
   3082         for (int x = 0; x < mCountX; x++) {
   3083             for (int y = 0; y < mCountY; y++) {
   3084                 mOccupied[x][y] = false;
   3085             }
   3086         }
   3087     }
   3088 
   3089     public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
   3090         markCellsAsUnoccupiedForView(view);
   3091         markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
   3092     }
   3093 
   3094     public void markCellsAsOccupiedForView(View view) {
   3095         markCellsAsOccupiedForView(view, mOccupied);
   3096     }
   3097     public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
   3098         if (view == null || view.getParent() != mShortcutsAndWidgets) return;
   3099         LayoutParams lp = (LayoutParams) view.getLayoutParams();
   3100         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
   3101     }
   3102 
   3103     public void markCellsAsUnoccupiedForView(View view) {
   3104         markCellsAsUnoccupiedForView(view, mOccupied);
   3105     }
   3106     public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
   3107         if (view == null || view.getParent() != mShortcutsAndWidgets) return;
   3108         LayoutParams lp = (LayoutParams) view.getLayoutParams();
   3109         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
   3110     }
   3111 
   3112     private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
   3113             boolean value) {
   3114         if (cellX < 0 || cellY < 0) return;
   3115         for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
   3116             for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
   3117                 occupied[x][y] = value;
   3118             }
   3119         }
   3120     }
   3121 
   3122     public int getDesiredWidth() {
   3123         return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
   3124                 (Math.max((mCountX - 1), 0) * mWidthGap);
   3125     }
   3126 
   3127     public int getDesiredHeight()  {
   3128         return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
   3129                 (Math.max((mCountY - 1), 0) * mHeightGap);
   3130     }
   3131 
   3132     public boolean isOccupied(int x, int y) {
   3133         if (x < mCountX && y < mCountY) {
   3134             return mOccupied[x][y];
   3135         } else {
   3136             throw new RuntimeException("Position exceeds the bound of this CellLayout");
   3137         }
   3138     }
   3139 
   3140     @Override
   3141     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
   3142         return new CellLayout.LayoutParams(getContext(), attrs);
   3143     }
   3144 
   3145     @Override
   3146     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
   3147         return p instanceof CellLayout.LayoutParams;
   3148     }
   3149 
   3150     @Override
   3151     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
   3152         return new CellLayout.LayoutParams(p);
   3153     }
   3154 
   3155     public static class CellLayoutAnimationController extends LayoutAnimationController {
   3156         public CellLayoutAnimationController(Animation animation, float delay) {
   3157             super(animation, delay);
   3158         }
   3159 
   3160         @Override
   3161         protected long getDelayForView(View view) {
   3162             return (int) (Math.random() * 150);
   3163         }
   3164     }
   3165 
   3166     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
   3167         /**
   3168          * Horizontal location of the item in the grid.
   3169          */
   3170         @ViewDebug.ExportedProperty
   3171         public int cellX;
   3172 
   3173         /**
   3174          * Vertical location of the item in the grid.
   3175          */
   3176         @ViewDebug.ExportedProperty
   3177         public int cellY;
   3178 
   3179         /**
   3180          * Temporary horizontal location of the item in the grid during reorder
   3181          */
   3182         public int tmpCellX;
   3183 
   3184         /**
   3185          * Temporary vertical location of the item in the grid during reorder
   3186          */
   3187         public int tmpCellY;
   3188 
   3189         /**
   3190          * Indicates that the temporary coordinates should be used to layout the items
   3191          */
   3192         public boolean useTmpCoords;
   3193 
   3194         /**
   3195          * Number of cells spanned horizontally by the item.
   3196          */
   3197         @ViewDebug.ExportedProperty
   3198         public int cellHSpan;
   3199 
   3200         /**
   3201          * Number of cells spanned vertically by the item.
   3202          */
   3203         @ViewDebug.ExportedProperty
   3204         public int cellVSpan;
   3205 
   3206         /**
   3207          * Indicates whether the item will set its x, y, width and height parameters freely,
   3208          * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
   3209          */
   3210         public boolean isLockedToGrid = true;
   3211 
   3212         /**
   3213          * Indicates whether this item can be reordered. Always true except in the case of the
   3214          * the AllApps button.
   3215          */
   3216         public boolean canReorder = true;
   3217 
   3218         // X coordinate of the view in the layout.
   3219         @ViewDebug.ExportedProperty
   3220         int x;
   3221         // Y coordinate of the view in the layout.
   3222         @ViewDebug.ExportedProperty
   3223         int y;
   3224 
   3225         boolean dropped;
   3226 
   3227         public LayoutParams(Context c, AttributeSet attrs) {
   3228             super(c, attrs);
   3229             cellHSpan = 1;
   3230             cellVSpan = 1;
   3231         }
   3232 
   3233         public LayoutParams(ViewGroup.LayoutParams source) {
   3234             super(source);
   3235             cellHSpan = 1;
   3236             cellVSpan = 1;
   3237         }
   3238 
   3239         public LayoutParams(LayoutParams source) {
   3240             super(source);
   3241             this.cellX = source.cellX;
   3242             this.cellY = source.cellY;
   3243             this.cellHSpan = source.cellHSpan;
   3244             this.cellVSpan = source.cellVSpan;
   3245         }
   3246 
   3247         public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
   3248             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
   3249             this.cellX = cellX;
   3250             this.cellY = cellY;
   3251             this.cellHSpan = cellHSpan;
   3252             this.cellVSpan = cellVSpan;
   3253         }
   3254 
   3255         public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
   3256                 boolean invertHorizontally, int colCount) {
   3257             if (isLockedToGrid) {
   3258                 final int myCellHSpan = cellHSpan;
   3259                 final int myCellVSpan = cellVSpan;
   3260                 int myCellX = useTmpCoords ? tmpCellX : cellX;
   3261                 int myCellY = useTmpCoords ? tmpCellY : cellY;
   3262 
   3263                 if (invertHorizontally) {
   3264                     myCellX = colCount - myCellX - cellHSpan;
   3265                 }
   3266 
   3267                 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
   3268                         leftMargin - rightMargin;
   3269                 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
   3270                         topMargin - bottomMargin;
   3271                 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
   3272                 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
   3273             }
   3274         }
   3275 
   3276         public String toString() {
   3277             return "(" + this.cellX + ", " + this.cellY + ")";
   3278         }
   3279 
   3280         public void setWidth(int width) {
   3281             this.width = width;
   3282         }
   3283 
   3284         public int getWidth() {
   3285             return width;
   3286         }
   3287 
   3288         public void setHeight(int height) {
   3289             this.height = height;
   3290         }
   3291 
   3292         public int getHeight() {
   3293             return height;
   3294         }
   3295 
   3296         public void setX(int x) {
   3297             this.x = x;
   3298         }
   3299 
   3300         public int getX() {
   3301             return x;
   3302         }
   3303 
   3304         public void setY(int y) {
   3305             this.y = y;
   3306         }
   3307 
   3308         public int getY() {
   3309             return y;
   3310         }
   3311     }
   3312 
   3313     // This class stores info for two purposes:
   3314     // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
   3315     //    its spanX, spanY, and the screen it is on
   3316     // 2. When long clicking on an empty cell in a CellLayout, we save information about the
   3317     //    cellX and cellY coordinates and which page was clicked. We then set this as a tag on
   3318     //    the CellLayout that was long clicked
   3319     static final class CellInfo {
   3320         View cell;
   3321         int cellX = -1;
   3322         int cellY = -1;
   3323         int spanX;
   3324         int spanY;
   3325         int screen;
   3326         long container;
   3327 
   3328         @Override
   3329         public String toString() {
   3330             return "Cell[view=" + (cell == null ? "null" : cell.getClass())
   3331                     + ", x=" + cellX + ", y=" + cellY + "]";
   3332         }
   3333     }
   3334 
   3335     public boolean lastDownOnOccupiedCell() {
   3336         return mLastDownOnOccupiedCell;
   3337     }
   3338 }
   3339