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