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