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