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.ObjectAnimator;
     22 import android.animation.PropertyValuesHolder;
     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.Paint;
     32 import android.graphics.Point;
     33 import android.graphics.PointF;
     34 import android.graphics.PorterDuff;
     35 import android.graphics.PorterDuffXfermode;
     36 import android.graphics.Rect;
     37 import android.graphics.RectF;
     38 import android.graphics.drawable.Drawable;
     39 import android.graphics.drawable.NinePatchDrawable;
     40 import android.util.AttributeSet;
     41 import android.util.Log;
     42 import android.view.MotionEvent;
     43 import android.view.View;
     44 import android.view.ViewDebug;
     45 import android.view.ViewGroup;
     46 import android.view.animation.Animation;
     47 import android.view.animation.DecelerateInterpolator;
     48 import android.view.animation.LayoutAnimationController;
     49 
     50 import com.android.launcher.R;
     51 import com.android.launcher2.FolderIcon.FolderRingAnimator;
     52 
     53 import java.util.ArrayList;
     54 import java.util.Arrays;
     55 import java.util.HashMap;
     56 
     57 public class CellLayout extends ViewGroup {
     58     static final String TAG = "CellLayout";
     59 
     60     private int mOriginalCellWidth;
     61     private int mOriginalCellHeight;
     62     private int mCellWidth;
     63     private int mCellHeight;
     64 
     65     private int mCountX;
     66     private int mCountY;
     67 
     68     private int mOriginalWidthGap;
     69     private int mOriginalHeightGap;
     70     private int mWidthGap;
     71     private int mHeightGap;
     72     private int mMaxGap;
     73     private boolean mScrollingTransformsDirty = false;
     74 
     75     private final Rect mRect = new Rect();
     76     private final CellInfo mCellInfo = new CellInfo();
     77 
     78     // These are temporary variables to prevent having to allocate a new object just to
     79     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
     80     private final int[] mTmpXY = new int[2];
     81     private final int[] mTmpPoint = new int[2];
     82     private final PointF mTmpPointF = new PointF();
     83     int[] mTempLocation = new int[2];
     84 
     85     boolean[][] mOccupied;
     86     private boolean mLastDownOnOccupiedCell = false;
     87 
     88     private OnTouchListener mInterceptTouchListener;
     89 
     90     private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
     91     private int[] mFolderLeaveBehindCell = {-1, -1};
     92 
     93     private int mForegroundAlpha = 0;
     94     private float mBackgroundAlpha;
     95     private float mBackgroundAlphaMultiplier = 1.0f;
     96 
     97     private Drawable mNormalBackground;
     98     private Drawable mActiveGlowBackground;
     99     private Drawable mOverScrollForegroundDrawable;
    100     private Drawable mOverScrollLeft;
    101     private Drawable mOverScrollRight;
    102     private Rect mBackgroundRect;
    103     private Rect mForegroundRect;
    104     private int mForegroundPadding;
    105 
    106     // If we're actively dragging something over this screen, mIsDragOverlapping is true
    107     private boolean mIsDragOverlapping = false;
    108     private final Point mDragCenter = new Point();
    109 
    110     // These arrays are used to implement the drag visualization on x-large screens.
    111     // They are used as circular arrays, indexed by mDragOutlineCurrent.
    112     private Point[] mDragOutlines = new Point[4];
    113     private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
    114     private InterruptibleInOutAnimator[] mDragOutlineAnims =
    115             new InterruptibleInOutAnimator[mDragOutlines.length];
    116 
    117     // Used as an index into the above 3 arrays; indicates which is the most current value.
    118     private int mDragOutlineCurrent = 0;
    119     private final Paint mDragOutlinePaint = new Paint();
    120 
    121     private BubbleTextView mPressedOrFocusedIcon;
    122 
    123     private Drawable mCrosshairsDrawable = null;
    124     private InterruptibleInOutAnimator mCrosshairsAnimator = null;
    125     private float mCrosshairsVisibility = 0.0f;
    126 
    127     private HashMap<CellLayout.LayoutParams, ObjectAnimator> mReorderAnimators = new
    128             HashMap<CellLayout.LayoutParams, ObjectAnimator>();
    129 
    130     // When a drag operation is in progress, holds the nearest cell to the touch point
    131     private final int[] mDragCell = new int[2];
    132 
    133     private boolean mDragging = false;
    134 
    135     private TimeInterpolator mEaseOutInterpolator;
    136     private CellLayoutChildren mChildren;
    137 
    138     public CellLayout(Context context) {
    139         this(context, null);
    140     }
    141 
    142     public CellLayout(Context context, AttributeSet attrs) {
    143         this(context, attrs, 0);
    144     }
    145 
    146     public CellLayout(Context context, AttributeSet attrs, int defStyle) {
    147         super(context, attrs, defStyle);
    148 
    149         // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
    150         // the user where a dragged item will land when dropped.
    151         setWillNotDraw(false);
    152 
    153         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
    154 
    155         mOriginalCellWidth =
    156             mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
    157         mOriginalCellHeight =
    158             mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
    159         mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0);
    160         mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0);
    161         mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0);
    162         mCountX = LauncherModel.getCellCountX();
    163         mCountY = LauncherModel.getCellCountY();
    164         mOccupied = new boolean[mCountX][mCountY];
    165 
    166         a.recycle();
    167 
    168         setAlwaysDrawnWithCacheEnabled(false);
    169 
    170         final Resources res = getResources();
    171 
    172         mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo);
    173         mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
    174 
    175         mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
    176         mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
    177         mForegroundPadding =
    178                 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
    179 
    180         mNormalBackground.setFilterBitmap(true);
    181         mActiveGlowBackground.setFilterBitmap(true);
    182 
    183         // Initialize the data structures used for the drag visualization.
    184 
    185         mCrosshairsDrawable = res.getDrawable(R.drawable.gardening_crosshairs);
    186         mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
    187 
    188         // Set up the animation for fading the crosshairs in and out
    189         int animDuration = res.getInteger(R.integer.config_crosshairsFadeInTime);
    190         mCrosshairsAnimator = new InterruptibleInOutAnimator(animDuration, 0.0f, 1.0f);
    191         mCrosshairsAnimator.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
    192             public void onAnimationUpdate(ValueAnimator animation) {
    193                 mCrosshairsVisibility = ((Float) animation.getAnimatedValue()).floatValue();
    194                 invalidate();
    195             }
    196         });
    197         mCrosshairsAnimator.getAnimator().setInterpolator(mEaseOutInterpolator);
    198 
    199         mDragCell[0] = mDragCell[1] = -1;
    200         for (int i = 0; i < mDragOutlines.length; i++) {
    201             mDragOutlines[i] = new Point(-1, -1);
    202         }
    203 
    204         // When dragging things around the home screens, we show a green outline of
    205         // where the item will land. The outlines gradually fade out, leaving a trail
    206         // behind the drag path.
    207         // Set up all the animations that are used to implement this fading.
    208         final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
    209         final float fromAlphaValue = 0;
    210         final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
    211 
    212         Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
    213 
    214         for (int i = 0; i < mDragOutlineAnims.length; i++) {
    215             final InterruptibleInOutAnimator anim =
    216                 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
    217             anim.getAnimator().setInterpolator(mEaseOutInterpolator);
    218             final int thisIndex = i;
    219             anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
    220                 public void onAnimationUpdate(ValueAnimator animation) {
    221                     final Bitmap outline = (Bitmap)anim.getTag();
    222 
    223                     // If an animation is started and then stopped very quickly, we can still
    224                     // get spurious updates we've cleared the tag. Guard against this.
    225                     if (outline == null) {
    226                         if (false) {
    227                             Object val = animation.getAnimatedValue();
    228                             Log.d(TAG, "anim " + thisIndex + " update: " + val +
    229                                      ", isStopped " + anim.isStopped());
    230                         }
    231                         // Try to prevent it from continuing to run
    232                         animation.cancel();
    233                     } else {
    234                         mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
    235                         final int left = mDragOutlines[thisIndex].x;
    236                         final int top = mDragOutlines[thisIndex].y;
    237                         CellLayout.this.invalidate(left, top,
    238                                 left + outline.getWidth(), top + outline.getHeight());
    239                     }
    240                 }
    241             });
    242             // The animation holds a reference to the drag outline bitmap as long is it's
    243             // running. This way the bitmap can be GCed when the animations are complete.
    244             anim.getAnimator().addListener(new AnimatorListenerAdapter() {
    245                 @Override
    246                 public void onAnimationEnd(Animator animation) {
    247                     if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
    248                         anim.setTag(null);
    249                     }
    250                 }
    251             });
    252             mDragOutlineAnims[i] = anim;
    253         }
    254 
    255         mBackgroundRect = new Rect();
    256         mForegroundRect = new Rect();
    257 
    258         mChildren = new CellLayoutChildren(context);
    259         mChildren.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
    260         addView(mChildren);
    261     }
    262 
    263     static int widthInPortrait(Resources r, int numCells) {
    264         // We use this method from Workspace to figure out how many rows/columns Launcher should
    265         // have. We ignore the left/right padding on CellLayout because it turns out in our design
    266         // the padding extends outside the visible screen size, but it looked fine anyway.
    267         int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
    268         int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
    269                 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
    270 
    271         return  minGap * (numCells - 1) + cellWidth * numCells;
    272     }
    273 
    274     static int heightInLandscape(Resources r, int numCells) {
    275         // We use this method from Workspace to figure out how many rows/columns Launcher should
    276         // have. We ignore the left/right padding on CellLayout because it turns out in our design
    277         // the padding extends outside the visible screen size, but it looked fine anyway.
    278         int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
    279         int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
    280                 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
    281 
    282         return minGap * (numCells - 1) + cellHeight * numCells;
    283     }
    284 
    285     public void enableHardwareLayers() {
    286         mChildren.enableHardwareLayers();
    287     }
    288 
    289     public void setGridSize(int x, int y) {
    290         mCountX = x;
    291         mCountY = y;
    292         mOccupied = new boolean[mCountX][mCountY];
    293         requestLayout();
    294     }
    295 
    296     private void invalidateBubbleTextView(BubbleTextView icon) {
    297         final int padding = icon.getPressedOrFocusedBackgroundPadding();
    298         invalidate(icon.getLeft() + getPaddingLeft() - padding,
    299                 icon.getTop() + getPaddingTop() - padding,
    300                 icon.getRight() + getPaddingLeft() + padding,
    301                 icon.getBottom() + getPaddingTop() + padding);
    302     }
    303 
    304     void setOverScrollAmount(float r, boolean left) {
    305         if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
    306             mOverScrollForegroundDrawable = mOverScrollLeft;
    307         } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
    308             mOverScrollForegroundDrawable = mOverScrollRight;
    309         }
    310 
    311         mForegroundAlpha = (int) Math.round((r * 255));
    312         mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
    313         invalidate();
    314     }
    315 
    316     void setPressedOrFocusedIcon(BubbleTextView icon) {
    317         // We draw the pressed or focused BubbleTextView's background in CellLayout because it
    318         // requires an expanded clip rect (due to the glow's blur radius)
    319         BubbleTextView oldIcon = mPressedOrFocusedIcon;
    320         mPressedOrFocusedIcon = icon;
    321         if (oldIcon != null) {
    322             invalidateBubbleTextView(oldIcon);
    323         }
    324         if (mPressedOrFocusedIcon != null) {
    325             invalidateBubbleTextView(mPressedOrFocusedIcon);
    326         }
    327     }
    328 
    329     public CellLayoutChildren getChildrenLayout() {
    330         if (getChildCount() > 0) {
    331             return (CellLayoutChildren) getChildAt(0);
    332         }
    333         return null;
    334     }
    335 
    336     void setIsDragOverlapping(boolean isDragOverlapping) {
    337         if (mIsDragOverlapping != isDragOverlapping) {
    338             mIsDragOverlapping = isDragOverlapping;
    339             invalidate();
    340         }
    341     }
    342 
    343     boolean getIsDragOverlapping() {
    344         return mIsDragOverlapping;
    345     }
    346 
    347     protected void setOverscrollTransformsDirty(boolean dirty) {
    348         mScrollingTransformsDirty = dirty;
    349     }
    350 
    351     protected void resetOverscrollTransforms() {
    352         if (mScrollingTransformsDirty) {
    353             setOverscrollTransformsDirty(false);
    354             setTranslationX(0);
    355             setRotationY(0);
    356             // It doesn't matter if we pass true or false here, the important thing is that we
    357             // pass 0, which results in the overscroll drawable not being drawn any more.
    358             setOverScrollAmount(0, false);
    359             setPivotX(getMeasuredWidth() / 2);
    360             setPivotY(getMeasuredHeight() / 2);
    361         }
    362     }
    363 
    364     @Override
    365     protected void onDraw(Canvas canvas) {
    366         // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
    367         // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
    368         // When we're small, we are either drawn normally or in the "accepts drops" state (during
    369         // a drag). However, we also drag the mini hover background *over* one of those two
    370         // backgrounds
    371         if (mBackgroundAlpha > 0.0f) {
    372             Drawable bg;
    373 
    374             if (mIsDragOverlapping) {
    375                 // In the mini case, we draw the active_glow bg *over* the active background
    376                 bg = mActiveGlowBackground;
    377             } else {
    378                 bg = mNormalBackground;
    379             }
    380 
    381             bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
    382             bg.setBounds(mBackgroundRect);
    383             bg.draw(canvas);
    384         }
    385 
    386         if (mCrosshairsVisibility > 0.0f) {
    387             final int countX = mCountX;
    388             final int countY = mCountY;
    389 
    390             final float MAX_ALPHA = 0.4f;
    391             final int MAX_VISIBLE_DISTANCE = 600;
    392             final float DISTANCE_MULTIPLIER = 0.002f;
    393 
    394             final Drawable d = mCrosshairsDrawable;
    395             final int width = d.getIntrinsicWidth();
    396             final int height = d.getIntrinsicHeight();
    397 
    398             int x = getPaddingLeft() - (mWidthGap / 2) - (width / 2);
    399             for (int col = 0; col <= countX; col++) {
    400                 int y = getPaddingTop() - (mHeightGap / 2) - (height / 2);
    401                 for (int row = 0; row <= countY; row++) {
    402                     mTmpPointF.set(x - mDragCenter.x, y - mDragCenter.y);
    403                     float dist = mTmpPointF.length();
    404                     // Crosshairs further from the drag point are more faint
    405                     float alpha = Math.min(MAX_ALPHA,
    406                             DISTANCE_MULTIPLIER * (MAX_VISIBLE_DISTANCE - dist));
    407                     if (alpha > 0.0f) {
    408                         d.setBounds(x, y, x + width, y + height);
    409                         d.setAlpha((int) (alpha * 255 * mCrosshairsVisibility));
    410                         d.draw(canvas);
    411                     }
    412                     y += mCellHeight + mHeightGap;
    413                 }
    414                 x += mCellWidth + mWidthGap;
    415             }
    416         }
    417 
    418         final Paint paint = mDragOutlinePaint;
    419         for (int i = 0; i < mDragOutlines.length; i++) {
    420             final float alpha = mDragOutlineAlphas[i];
    421             if (alpha > 0) {
    422                 final Point p = mDragOutlines[i];
    423                 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
    424                 paint.setAlpha((int)(alpha + .5f));
    425                 canvas.drawBitmap(b, p.x, p.y, paint);
    426             }
    427         }
    428 
    429         // We draw the pressed or focused BubbleTextView's background in CellLayout because it
    430         // requires an expanded clip rect (due to the glow's blur radius)
    431         if (mPressedOrFocusedIcon != null) {
    432             final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
    433             final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
    434             if (b != null) {
    435                 canvas.drawBitmap(b,
    436                         mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
    437                         mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
    438                         null);
    439             }
    440         }
    441 
    442         // The folder outer / inner ring image(s)
    443         for (int i = 0; i < mFolderOuterRings.size(); i++) {
    444             FolderRingAnimator fra = mFolderOuterRings.get(i);
    445 
    446             // Draw outer ring
    447             Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
    448             int width = (int) fra.getOuterRingSize();
    449             int height = width;
    450             cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
    451 
    452             int centerX = mTempLocation[0] + mCellWidth / 2;
    453             int centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2;
    454 
    455             canvas.save();
    456             canvas.translate(centerX - width / 2, centerY - height / 2);
    457             d.setBounds(0, 0, width, height);
    458             d.draw(canvas);
    459             canvas.restore();
    460 
    461             // Draw inner ring
    462             d = FolderRingAnimator.sSharedInnerRingDrawable;
    463             width = (int) fra.getInnerRingSize();
    464             height = width;
    465             cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
    466 
    467             centerX = mTempLocation[0] + mCellWidth / 2;
    468             centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2;
    469             canvas.save();
    470             canvas.translate(centerX - width / 2, centerY - width / 2);
    471             d.setBounds(0, 0, width, height);
    472             d.draw(canvas);
    473             canvas.restore();
    474         }
    475 
    476         if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
    477             Drawable d = FolderIcon.sSharedFolderLeaveBehind;
    478             int width = d.getIntrinsicWidth();
    479             int height = d.getIntrinsicHeight();
    480 
    481             cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
    482             int centerX = mTempLocation[0] + mCellWidth / 2;
    483             int centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2;
    484 
    485             canvas.save();
    486             canvas.translate(centerX - width / 2, centerY - width / 2);
    487             d.setBounds(0, 0, width, height);
    488             d.draw(canvas);
    489             canvas.restore();
    490         }
    491     }
    492 
    493     @Override
    494     protected void dispatchDraw(Canvas canvas) {
    495         super.dispatchDraw(canvas);
    496         if (mForegroundAlpha > 0) {
    497             mOverScrollForegroundDrawable.setBounds(mForegroundRect);
    498             Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
    499             p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
    500             mOverScrollForegroundDrawable.draw(canvas);
    501             p.setXfermode(null);
    502         }
    503     }
    504 
    505     public void showFolderAccept(FolderRingAnimator fra) {
    506         mFolderOuterRings.add(fra);
    507     }
    508 
    509     public void hideFolderAccept(FolderRingAnimator fra) {
    510         if (mFolderOuterRings.contains(fra)) {
    511             mFolderOuterRings.remove(fra);
    512         }
    513         invalidate();
    514     }
    515 
    516     public void setFolderLeaveBehindCell(int x, int y) {
    517         mFolderLeaveBehindCell[0] = x;
    518         mFolderLeaveBehindCell[1] = y;
    519         invalidate();
    520     }
    521 
    522     public void clearFolderLeaveBehind() {
    523         mFolderLeaveBehindCell[0] = -1;
    524         mFolderLeaveBehindCell[1] = -1;
    525         invalidate();
    526     }
    527 
    528     @Override
    529     public boolean shouldDelayChildPressedState() {
    530         return false;
    531     }
    532 
    533     @Override
    534     public void cancelLongPress() {
    535         super.cancelLongPress();
    536 
    537         // Cancel long press for all children
    538         final int count = getChildCount();
    539         for (int i = 0; i < count; i++) {
    540             final View child = getChildAt(i);
    541             child.cancelLongPress();
    542         }
    543     }
    544 
    545     public void setOnInterceptTouchListener(View.OnTouchListener listener) {
    546         mInterceptTouchListener = listener;
    547     }
    548 
    549     int getCountX() {
    550         return mCountX;
    551     }
    552 
    553     int getCountY() {
    554         return mCountY;
    555     }
    556 
    557     public boolean addViewToCellLayout(
    558             View child, int index, int childId, LayoutParams params, boolean markCells) {
    559         final LayoutParams lp = params;
    560 
    561         // Generate an id for each view, this assumes we have at most 256x256 cells
    562         // per workspace screen
    563         if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
    564             // If the horizontal or vertical span is set to -1, it is taken to
    565             // mean that it spans the extent of the CellLayout
    566             if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
    567             if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
    568 
    569             child.setId(childId);
    570 
    571             mChildren.addView(child, index, lp);
    572 
    573             if (markCells) markCellsAsOccupiedForView(child);
    574 
    575             return true;
    576         }
    577         return false;
    578     }
    579 
    580     @Override
    581     public void removeAllViews() {
    582         clearOccupiedCells();
    583         mChildren.removeAllViews();
    584     }
    585 
    586     @Override
    587     public void removeAllViewsInLayout() {
    588         if (mChildren.getChildCount() > 0) {
    589             clearOccupiedCells();
    590             mChildren.removeAllViewsInLayout();
    591         }
    592     }
    593 
    594     public void removeViewWithoutMarkingCells(View view) {
    595         mChildren.removeView(view);
    596     }
    597 
    598     @Override
    599     public void removeView(View view) {
    600         markCellsAsUnoccupiedForView(view);
    601         mChildren.removeView(view);
    602     }
    603 
    604     @Override
    605     public void removeViewAt(int index) {
    606         markCellsAsUnoccupiedForView(mChildren.getChildAt(index));
    607         mChildren.removeViewAt(index);
    608     }
    609 
    610     @Override
    611     public void removeViewInLayout(View view) {
    612         markCellsAsUnoccupiedForView(view);
    613         mChildren.removeViewInLayout(view);
    614     }
    615 
    616     @Override
    617     public void removeViews(int start, int count) {
    618         for (int i = start; i < start + count; i++) {
    619             markCellsAsUnoccupiedForView(mChildren.getChildAt(i));
    620         }
    621         mChildren.removeViews(start, count);
    622     }
    623 
    624     @Override
    625     public void removeViewsInLayout(int start, int count) {
    626         for (int i = start; i < start + count; i++) {
    627             markCellsAsUnoccupiedForView(mChildren.getChildAt(i));
    628         }
    629         mChildren.removeViewsInLayout(start, count);
    630     }
    631 
    632     public void drawChildren(Canvas canvas) {
    633         mChildren.draw(canvas);
    634     }
    635 
    636     void buildChildrenLayer() {
    637         mChildren.buildLayer();
    638     }
    639 
    640     @Override
    641     protected void onAttachedToWindow() {
    642         super.onAttachedToWindow();
    643         mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
    644     }
    645 
    646     public void setTagToCellInfoForPoint(int touchX, int touchY) {
    647         final CellInfo cellInfo = mCellInfo;
    648         final Rect frame = mRect;
    649         final int x = touchX + mScrollX;
    650         final int y = touchY + mScrollY;
    651         final int count = mChildren.getChildCount();
    652 
    653         boolean found = false;
    654         for (int i = count - 1; i >= 0; i--) {
    655             final View child = mChildren.getChildAt(i);
    656             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    657 
    658             if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
    659                     lp.isLockedToGrid) {
    660                 child.getHitRect(frame);
    661 
    662                 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
    663                 // offset that by this CellLayout's padding to test an (x,y) point that is relative
    664                 // to this view.
    665                 frame.offset(mPaddingLeft, mPaddingTop);
    666 
    667                 if (frame.contains(x, y)) {
    668                     cellInfo.cell = child;
    669                     cellInfo.cellX = lp.cellX;
    670                     cellInfo.cellY = lp.cellY;
    671                     cellInfo.spanX = lp.cellHSpan;
    672                     cellInfo.spanY = lp.cellVSpan;
    673                     found = true;
    674                     break;
    675                 }
    676             }
    677         }
    678 
    679         mLastDownOnOccupiedCell = found;
    680 
    681         if (!found) {
    682             final int cellXY[] = mTmpXY;
    683             pointToCellExact(x, y, cellXY);
    684 
    685             cellInfo.cell = null;
    686             cellInfo.cellX = cellXY[0];
    687             cellInfo.cellY = cellXY[1];
    688             cellInfo.spanX = 1;
    689             cellInfo.spanY = 1;
    690         }
    691         setTag(cellInfo);
    692     }
    693 
    694     @Override
    695     public boolean onInterceptTouchEvent(MotionEvent ev) {
    696         // First we clear the tag to ensure that on every touch down we start with a fresh slate,
    697         // even in the case where we return early. Not clearing here was causing bugs whereby on
    698         // long-press we'd end up picking up an item from a previous drag operation.
    699         final int action = ev.getAction();
    700 
    701         if (action == MotionEvent.ACTION_DOWN) {
    702             clearTagCellInfo();
    703         }
    704 
    705         if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
    706             return true;
    707         }
    708 
    709         if (action == MotionEvent.ACTION_DOWN) {
    710             setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
    711         }
    712         return false;
    713     }
    714 
    715     private void clearTagCellInfo() {
    716         final CellInfo cellInfo = mCellInfo;
    717         cellInfo.cell = null;
    718         cellInfo.cellX = -1;
    719         cellInfo.cellY = -1;
    720         cellInfo.spanX = 0;
    721         cellInfo.spanY = 0;
    722         setTag(cellInfo);
    723     }
    724 
    725     public CellInfo getTag() {
    726         return (CellInfo) super.getTag();
    727     }
    728 
    729     /**
    730      * Given a point, return the cell that strictly encloses that point
    731      * @param x X coordinate of the point
    732      * @param y Y coordinate of the point
    733      * @param result Array of 2 ints to hold the x and y coordinate of the cell
    734      */
    735     void pointToCellExact(int x, int y, int[] result) {
    736         final int hStartPadding = getPaddingLeft();
    737         final int vStartPadding = getPaddingTop();
    738 
    739         result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
    740         result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
    741 
    742         final int xAxis = mCountX;
    743         final int yAxis = mCountY;
    744 
    745         if (result[0] < 0) result[0] = 0;
    746         if (result[0] >= xAxis) result[0] = xAxis - 1;
    747         if (result[1] < 0) result[1] = 0;
    748         if (result[1] >= yAxis) result[1] = yAxis - 1;
    749     }
    750 
    751     /**
    752      * Given a point, return the cell that most closely 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 pointToCellRounded(int x, int y, int[] result) {
    758         pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
    759     }
    760 
    761     /**
    762      * Given a cell coordinate, return the point that represents the upper left corner of that cell
    763      *
    764      * @param cellX X coordinate of the cell
    765      * @param cellY Y coordinate of the cell
    766      *
    767      * @param result Array of 2 ints to hold the x and y coordinate of the point
    768      */
    769     void cellToPoint(int cellX, int cellY, int[] result) {
    770         final int hStartPadding = getPaddingLeft();
    771         final int vStartPadding = getPaddingTop();
    772 
    773         result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
    774         result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
    775     }
    776 
    777     /**
    778      * Given a cell coordinate, return the point that represents the upper left corner of that cell
    779      *
    780      * @param cellX X coordinate of the cell
    781      * @param cellY Y coordinate of the cell
    782      *
    783      * @param result Array of 2 ints to hold the x and y coordinate of the point
    784      */
    785     void cellToCenterPoint(int cellX, int cellY, int[] result) {
    786         final int hStartPadding = getPaddingLeft();
    787         final int vStartPadding = getPaddingTop();
    788 
    789         result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) + mCellWidth / 2;
    790         result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) + mCellHeight / 2;
    791     }
    792 
    793     int getCellWidth() {
    794         return mCellWidth;
    795     }
    796 
    797     int getCellHeight() {
    798         return mCellHeight;
    799     }
    800 
    801     int getWidthGap() {
    802         return mWidthGap;
    803     }
    804 
    805     int getHeightGap() {
    806         return mHeightGap;
    807     }
    808 
    809     Rect getContentRect(Rect r) {
    810         if (r == null) {
    811             r = new Rect();
    812         }
    813         int left = getPaddingLeft();
    814         int top = getPaddingTop();
    815         int right = left + getWidth() - mPaddingLeft - mPaddingRight;
    816         int bottom = top + getHeight() - mPaddingTop - mPaddingBottom;
    817         r.set(left, top, right, bottom);
    818         return r;
    819     }
    820 
    821     @Override
    822     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    823         // TODO: currently ignoring padding
    824 
    825         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    826         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    827 
    828         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    829         int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
    830 
    831         if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
    832             throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
    833         }
    834 
    835         int numWidthGaps = mCountX - 1;
    836         int numHeightGaps = mCountY - 1;
    837 
    838         if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
    839             int hSpace = widthSpecSize - mPaddingLeft - mPaddingRight;
    840             int vSpace = heightSpecSize - mPaddingTop - mPaddingBottom;
    841             int hFreeSpace = hSpace - (mCountX * mOriginalCellWidth);
    842             int vFreeSpace = vSpace - (mCountY * mOriginalCellHeight);
    843             mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
    844             mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
    845             mChildren.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
    846         } else {
    847             mWidthGap = mOriginalWidthGap;
    848             mHeightGap = mOriginalHeightGap;
    849         }
    850 
    851         // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
    852         int newWidth = widthSpecSize;
    853         int newHeight = heightSpecSize;
    854         if (widthSpecMode == MeasureSpec.AT_MOST) {
    855             newWidth = mPaddingLeft + mPaddingRight + (mCountX * mCellWidth) +
    856                 ((mCountX - 1) * mWidthGap);
    857             newHeight = mPaddingTop + mPaddingBottom + (mCountY * mCellHeight) +
    858                 ((mCountY - 1) * mHeightGap);
    859             setMeasuredDimension(newWidth, newHeight);
    860         }
    861 
    862         int count = getChildCount();
    863         for (int i = 0; i < count; i++) {
    864             View child = getChildAt(i);
    865             int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - mPaddingLeft -
    866                     mPaddingRight, MeasureSpec.EXACTLY);
    867             int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - mPaddingTop -
    868                     mPaddingBottom, MeasureSpec.EXACTLY);
    869             child.measure(childWidthMeasureSpec, childheightMeasureSpec);
    870         }
    871         setMeasuredDimension(newWidth, newHeight);
    872     }
    873 
    874     @Override
    875     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    876         int count = getChildCount();
    877         for (int i = 0; i < count; i++) {
    878             View child = getChildAt(i);
    879             child.layout(mPaddingLeft, mPaddingTop,
    880                     r - l - mPaddingRight, b - t - mPaddingBottom);
    881         }
    882     }
    883 
    884     @Override
    885     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    886         super.onSizeChanged(w, h, oldw, oldh);
    887         mBackgroundRect.set(0, 0, w, h);
    888         mForegroundRect.set(mForegroundPadding, mForegroundPadding,
    889                 w - 2 * mForegroundPadding, h - 2 * mForegroundPadding);
    890     }
    891 
    892     @Override
    893     protected void setChildrenDrawingCacheEnabled(boolean enabled) {
    894         mChildren.setChildrenDrawingCacheEnabled(enabled);
    895     }
    896 
    897     @Override
    898     protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
    899         mChildren.setChildrenDrawnWithCacheEnabled(enabled);
    900     }
    901 
    902     public float getBackgroundAlpha() {
    903         return mBackgroundAlpha;
    904     }
    905 
    906     public void setFastBackgroundAlpha(float alpha) {
    907         mBackgroundAlpha = alpha;
    908     }
    909 
    910     public void setBackgroundAlphaMultiplier(float multiplier) {
    911         mBackgroundAlphaMultiplier = multiplier;
    912     }
    913 
    914     public float getBackgroundAlphaMultiplier() {
    915         return mBackgroundAlphaMultiplier;
    916     }
    917 
    918     public void setBackgroundAlpha(float alpha) {
    919         mBackgroundAlpha = alpha;
    920         invalidate();
    921     }
    922 
    923     // Need to return true to let the view system know we know how to handle alpha-- this is
    924     // because when our children have an alpha of 0.0f, they are still rendering their "dimmed"
    925     // versions
    926     @Override
    927     protected boolean onSetAlpha(int alpha) {
    928         return true;
    929     }
    930 
    931     public void setAlpha(float alpha) {
    932         setChildrenAlpha(alpha);
    933         super.setAlpha(alpha);
    934     }
    935 
    936     public void setFastAlpha(float alpha) {
    937         setFastChildrenAlpha(alpha);
    938         super.setFastAlpha(alpha);
    939     }
    940 
    941     private void setChildrenAlpha(float alpha) {
    942         final int childCount = getChildCount();
    943         for (int i = 0; i < childCount; i++) {
    944             getChildAt(i).setAlpha(alpha);
    945         }
    946     }
    947 
    948     private void setFastChildrenAlpha(float alpha) {
    949         final int childCount = getChildCount();
    950         for (int i = 0; i < childCount; i++) {
    951             getChildAt(i).setFastAlpha(alpha);
    952         }
    953     }
    954 
    955     public View getChildAt(int x, int y) {
    956         return mChildren.getChildAt(x, y);
    957     }
    958 
    959     public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
    960             int delay) {
    961         CellLayoutChildren clc = getChildrenLayout();
    962         if (clc.indexOfChild(child) != -1 && !mOccupied[cellX][cellY]) {
    963             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    964             final ItemInfo info = (ItemInfo) child.getTag();
    965 
    966             // We cancel any existing animations
    967             if (mReorderAnimators.containsKey(lp)) {
    968                 mReorderAnimators.get(lp).cancel();
    969                 mReorderAnimators.remove(lp);
    970             }
    971 
    972             int oldX = lp.x;
    973             int oldY = lp.y;
    974             mOccupied[lp.cellX][lp.cellY] = false;
    975             mOccupied[cellX][cellY] = true;
    976 
    977             lp.isLockedToGrid = true;
    978             lp.cellX = info.cellX = cellX;
    979             lp.cellY = info.cellY = cellY;
    980             clc.setupLp(lp);
    981             lp.isLockedToGrid = false;
    982             int newX = lp.x;
    983             int newY = lp.y;
    984 
    985             lp.x = oldX;
    986             lp.y = oldY;
    987             child.requestLayout();
    988 
    989             PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", oldX, newX);
    990             PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", oldY, newY);
    991             ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, x, y);
    992             oa.setDuration(duration);
    993             mReorderAnimators.put(lp, oa);
    994             oa.addUpdateListener(new AnimatorUpdateListener() {
    995                 public void onAnimationUpdate(ValueAnimator animation) {
    996                     child.requestLayout();
    997                 }
    998             });
    999             oa.addListener(new AnimatorListenerAdapter() {
   1000                 boolean cancelled = false;
   1001                 public void onAnimationEnd(Animator animation) {
   1002                     // If the animation was cancelled, it means that another animation
   1003                     // has interrupted this one, and we don't want to lock the item into
   1004                     // place just yet.
   1005                     if (!cancelled) {
   1006                         lp.isLockedToGrid = true;
   1007                     }
   1008                     if (mReorderAnimators.containsKey(lp)) {
   1009                         mReorderAnimators.remove(lp);
   1010                     }
   1011                 }
   1012                 public void onAnimationCancel(Animator animation) {
   1013                     cancelled = true;
   1014                 }
   1015             });
   1016             oa.setStartDelay(delay);
   1017             oa.start();
   1018             return true;
   1019         }
   1020         return false;
   1021     }
   1022 
   1023     /**
   1024      * Estimate where the top left cell of the dragged item will land if it is dropped.
   1025      *
   1026      * @param originX The X value of the top left corner of the item
   1027      * @param originY The Y value of the top left corner of the item
   1028      * @param spanX The number of horizontal cells that the item spans
   1029      * @param spanY The number of vertical cells that the item spans
   1030      * @param result The estimated drop cell X and Y.
   1031      */
   1032     void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
   1033         final int countX = mCountX;
   1034         final int countY = mCountY;
   1035 
   1036         // pointToCellRounded takes the top left of a cell but will pad that with
   1037         // cellWidth/2 and cellHeight/2 when finding the matching cell
   1038         pointToCellRounded(originX, originY, result);
   1039 
   1040         // If the item isn't fully on this screen, snap to the edges
   1041         int rightOverhang = result[0] + spanX - countX;
   1042         if (rightOverhang > 0) {
   1043             result[0] -= rightOverhang; // Snap to right
   1044         }
   1045         result[0] = Math.max(0, result[0]); // Snap to left
   1046         int bottomOverhang = result[1] + spanY - countY;
   1047         if (bottomOverhang > 0) {
   1048             result[1] -= bottomOverhang; // Snap to bottom
   1049         }
   1050         result[1] = Math.max(0, result[1]); // Snap to top
   1051     }
   1052 
   1053     void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY,
   1054             int spanX, int spanY, Point dragOffset, Rect dragRegion) {
   1055 
   1056         final int oldDragCellX = mDragCell[0];
   1057         final int oldDragCellY = mDragCell[1];
   1058         final int[] nearest = findNearestVacantArea(originX, originY, spanX, spanY, v, mDragCell);
   1059         if (v != null && dragOffset == null) {
   1060             mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
   1061         } else {
   1062             mDragCenter.set(originX, originY);
   1063         }
   1064 
   1065         if (dragOutline == null && v == null) {
   1066             if (mCrosshairsDrawable != null) {
   1067                 invalidate();
   1068             }
   1069             return;
   1070         }
   1071 
   1072         if (nearest != null && (nearest[0] != oldDragCellX || nearest[1] != oldDragCellY)) {
   1073             // Find the top left corner of the rect the object will occupy
   1074             final int[] topLeft = mTmpPoint;
   1075             cellToPoint(nearest[0], nearest[1], topLeft);
   1076 
   1077             int left = topLeft[0];
   1078             int top = topLeft[1];
   1079 
   1080             if (v != null && dragOffset == null) {
   1081                 // When drawing the drag outline, it did not account for margin offsets
   1082                 // added by the view's parent.
   1083                 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
   1084                 left += lp.leftMargin;
   1085                 top += lp.topMargin;
   1086 
   1087                 // Offsets due to the size difference between the View and the dragOutline.
   1088                 // There is a size difference to account for the outer blur, which may lie
   1089                 // outside the bounds of the view.
   1090                 top += (v.getHeight() - dragOutline.getHeight()) / 2;
   1091                 // We center about the x axis
   1092                 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
   1093                         - dragOutline.getWidth()) / 2;
   1094             } else {
   1095                 if (dragOffset != null && dragRegion != null) {
   1096                     // Center the drag region *horizontally* in the cell and apply a drag
   1097                     // outline offset
   1098                     left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
   1099                              - dragRegion.width()) / 2;
   1100                     top += dragOffset.y;
   1101                 } else {
   1102                     // Center the drag outline in the cell
   1103                     left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
   1104                             - dragOutline.getWidth()) / 2;
   1105                     top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
   1106                             - dragOutline.getHeight()) / 2;
   1107                 }
   1108             }
   1109 
   1110             final int oldIndex = mDragOutlineCurrent;
   1111             mDragOutlineAnims[oldIndex].animateOut();
   1112             mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
   1113 
   1114             mDragOutlines[mDragOutlineCurrent].set(left, top);
   1115             mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
   1116             mDragOutlineAnims[mDragOutlineCurrent].animateIn();
   1117         }
   1118 
   1119         // If we are drawing crosshairs, the entire CellLayout needs to be invalidated
   1120         if (mCrosshairsDrawable != null) {
   1121             invalidate();
   1122         }
   1123     }
   1124 
   1125     public void clearDragOutlines() {
   1126         final int oldIndex = mDragOutlineCurrent;
   1127         mDragOutlineAnims[oldIndex].animateOut();
   1128         mDragCell[0] = -1;
   1129         mDragCell[1] = -1;
   1130     }
   1131 
   1132     /**
   1133      * Find a vacant area that will fit the given bounds nearest the requested
   1134      * cell location. Uses Euclidean distance to score multiple vacant areas.
   1135      *
   1136      * @param pixelX The X location at which you want to search for a vacant area.
   1137      * @param pixelY The Y location at which you want to search for a vacant area.
   1138      * @param spanX Horizontal span of the object.
   1139      * @param spanY Vertical span of the object.
   1140      * @param result Array in which to place the result, or null (in which case a new array will
   1141      *        be allocated)
   1142      * @return The X, Y cell of a vacant area that can contain this object,
   1143      *         nearest the requested location.
   1144      */
   1145     int[] findNearestVacantArea(
   1146             int pixelX, int pixelY, int spanX, int spanY, int[] result) {
   1147         return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
   1148     }
   1149 
   1150     /**
   1151      * Find a vacant area that will fit the given bounds nearest the requested
   1152      * cell location. Uses Euclidean distance to score multiple vacant areas.
   1153      *
   1154      * @param pixelX The X location at which you want to search for a vacant area.
   1155      * @param pixelY The Y location at which you want to search for a vacant area.
   1156      * @param spanX Horizontal span of the object.
   1157      * @param spanY Vertical span of the object.
   1158      * @param ignoreOccupied If true, the result can be an occupied cell
   1159      * @param result Array in which to place the result, or null (in which case a new array will
   1160      *        be allocated)
   1161      * @return The X, Y cell of a vacant area that can contain this object,
   1162      *         nearest the requested location.
   1163      */
   1164     int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
   1165             boolean ignoreOccupied, int[] result) {
   1166         // mark space take by ignoreView as available (method checks if ignoreView is null)
   1167         markCellsAsUnoccupiedForView(ignoreView);
   1168 
   1169         // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
   1170         // to the center of the item, but we are searching based on the top-left cell, so
   1171         // we translate the point over to correspond to the top-left.
   1172         pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
   1173         pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
   1174 
   1175         // Keep track of best-scoring drop area
   1176         final int[] bestXY = result != null ? result : new int[2];
   1177         double bestDistance = Double.MAX_VALUE;
   1178 
   1179         final int countX = mCountX;
   1180         final int countY = mCountY;
   1181         final boolean[][] occupied = mOccupied;
   1182 
   1183         for (int y = 0; y < countY - (spanY - 1); y++) {
   1184             inner:
   1185             for (int x = 0; x < countX - (spanX - 1); x++) {
   1186                 if (ignoreOccupied) {
   1187                     for (int i = 0; i < spanX; i++) {
   1188                         for (int j = 0; j < spanY; j++) {
   1189                             if (occupied[x + i][y + j]) {
   1190                                 // small optimization: we can skip to after the column we
   1191                                 // just found an occupied cell
   1192                                 x += i;
   1193                                 continue inner;
   1194                             }
   1195                         }
   1196                     }
   1197                 }
   1198                 final int[] cellXY = mTmpXY;
   1199                 cellToCenterPoint(x, y, cellXY);
   1200 
   1201                 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
   1202                         + Math.pow(cellXY[1] - pixelY, 2));
   1203                 if (distance <= bestDistance) {
   1204                     bestDistance = distance;
   1205                     bestXY[0] = x;
   1206                     bestXY[1] = y;
   1207                 }
   1208             }
   1209         }
   1210         // re-mark space taken by ignoreView as occupied
   1211         markCellsAsOccupiedForView(ignoreView);
   1212 
   1213         // Return -1, -1 if no suitable location found
   1214         if (bestDistance == Double.MAX_VALUE) {
   1215             bestXY[0] = -1;
   1216             bestXY[1] = -1;
   1217         }
   1218         return bestXY;
   1219     }
   1220 
   1221     /**
   1222      * Find a vacant area that will fit the given bounds nearest the requested
   1223      * cell location. Uses Euclidean distance to score multiple vacant areas.
   1224      *
   1225      * @param pixelX The X location at which you want to search for a vacant area.
   1226      * @param pixelY The Y location at which you want to search for a vacant area.
   1227      * @param spanX Horizontal span of the object.
   1228      * @param spanY Vertical span of the object.
   1229      * @param ignoreView Considers space occupied by this view as unoccupied
   1230      * @param result Previously returned value to possibly recycle.
   1231      * @return The X, Y cell of a vacant area that can contain this object,
   1232      *         nearest the requested location.
   1233      */
   1234     int[] findNearestVacantArea(
   1235             int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
   1236         return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
   1237     }
   1238 
   1239     /**
   1240      * Find a starting cell position that will fit the given bounds nearest the requested
   1241      * cell location. Uses Euclidean distance to score multiple vacant areas.
   1242      *
   1243      * @param pixelX The X location at which you want to search for a vacant area.
   1244      * @param pixelY The Y location at which you want to search for a vacant area.
   1245      * @param spanX Horizontal span of the object.
   1246      * @param spanY Vertical span of the object.
   1247      * @param ignoreView Considers space occupied by this view as unoccupied
   1248      * @param result Previously returned value to possibly recycle.
   1249      * @return The X, Y cell of a vacant area that can contain this object,
   1250      *         nearest the requested location.
   1251      */
   1252     int[] findNearestArea(
   1253             int pixelX, int pixelY, int spanX, int spanY, int[] result) {
   1254         return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
   1255     }
   1256 
   1257     boolean existsEmptyCell() {
   1258         return findCellForSpan(null, 1, 1);
   1259     }
   1260 
   1261     /**
   1262      * Finds the upper-left coordinate of the first rectangle in the grid that can
   1263      * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
   1264      * then this method will only return coordinates for rectangles that contain the cell
   1265      * (intersectX, intersectY)
   1266      *
   1267      * @param cellXY The array that will contain the position of a vacant cell if such a cell
   1268      *               can be found.
   1269      * @param spanX The horizontal span of the cell we want to find.
   1270      * @param spanY The vertical span of the cell we want to find.
   1271      *
   1272      * @return True if a vacant cell of the specified dimension was found, false otherwise.
   1273      */
   1274     boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
   1275         return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null);
   1276     }
   1277 
   1278     /**
   1279      * Like above, but ignores any cells occupied by the item "ignoreView"
   1280      *
   1281      * @param cellXY The array that will contain the position of a vacant cell if such a cell
   1282      *               can be found.
   1283      * @param spanX The horizontal span of the cell we want to find.
   1284      * @param spanY The vertical span of the cell we want to find.
   1285      * @param ignoreView The home screen item we should treat as not occupying any space
   1286      * @return
   1287      */
   1288     boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
   1289         return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, ignoreView);
   1290     }
   1291 
   1292     /**
   1293      * Like above, but if intersectX and intersectY are not -1, then this method will try to
   1294      * return coordinates for rectangles that contain the cell [intersectX, intersectY]
   1295      *
   1296      * @param spanX The horizontal span of the cell we want to find.
   1297      * @param spanY The vertical span of the cell we want to find.
   1298      * @param ignoreView The home screen item we should treat as not occupying any space
   1299      * @param intersectX The X coordinate of the cell that we should try to overlap
   1300      * @param intersectX The Y coordinate of the cell that we should try to overlap
   1301      *
   1302      * @return True if a vacant cell of the specified dimension was found, false otherwise.
   1303      */
   1304     boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
   1305             int intersectX, int intersectY) {
   1306         return findCellForSpanThatIntersectsIgnoring(
   1307                 cellXY, spanX, spanY, intersectX, intersectY, null);
   1308     }
   1309 
   1310     /**
   1311      * The superset of the above two methods
   1312      */
   1313     boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
   1314             int intersectX, int intersectY, View ignoreView) {
   1315         // mark space take by ignoreView as available (method checks if ignoreView is null)
   1316         markCellsAsUnoccupiedForView(ignoreView);
   1317 
   1318         boolean foundCell = false;
   1319         while (true) {
   1320             int startX = 0;
   1321             if (intersectX >= 0) {
   1322                 startX = Math.max(startX, intersectX - (spanX - 1));
   1323             }
   1324             int endX = mCountX - (spanX - 1);
   1325             if (intersectX >= 0) {
   1326                 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
   1327             }
   1328             int startY = 0;
   1329             if (intersectY >= 0) {
   1330                 startY = Math.max(startY, intersectY - (spanY - 1));
   1331             }
   1332             int endY = mCountY - (spanY - 1);
   1333             if (intersectY >= 0) {
   1334                 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
   1335             }
   1336 
   1337             for (int y = startY; y < endY && !foundCell; y++) {
   1338                 inner:
   1339                 for (int x = startX; x < endX; x++) {
   1340                     for (int i = 0; i < spanX; i++) {
   1341                         for (int j = 0; j < spanY; j++) {
   1342                             if (mOccupied[x + i][y + j]) {
   1343                                 // small optimization: we can skip to after the column we just found
   1344                                 // an occupied cell
   1345                                 x += i;
   1346                                 continue inner;
   1347                             }
   1348                         }
   1349                     }
   1350                     if (cellXY != null) {
   1351                         cellXY[0] = x;
   1352                         cellXY[1] = y;
   1353                     }
   1354                     foundCell = true;
   1355                     break;
   1356                 }
   1357             }
   1358             if (intersectX == -1 && intersectY == -1) {
   1359                 break;
   1360             } else {
   1361                 // if we failed to find anything, try again but without any requirements of
   1362                 // intersecting
   1363                 intersectX = -1;
   1364                 intersectY = -1;
   1365                 continue;
   1366             }
   1367         }
   1368 
   1369         // re-mark space taken by ignoreView as occupied
   1370         markCellsAsOccupiedForView(ignoreView);
   1371         return foundCell;
   1372     }
   1373 
   1374     /**
   1375      * A drag event has begun over this layout.
   1376      * It may have begun over this layout (in which case onDragChild is called first),
   1377      * or it may have begun on another layout.
   1378      */
   1379     void onDragEnter() {
   1380         if (!mDragging) {
   1381             // Fade in the drag indicators
   1382             if (mCrosshairsAnimator != null) {
   1383                 mCrosshairsAnimator.animateIn();
   1384             }
   1385         }
   1386         mDragging = true;
   1387     }
   1388 
   1389     /**
   1390      * Called when drag has left this CellLayout or has been completed (successfully or not)
   1391      */
   1392     void onDragExit() {
   1393         // This can actually be called when we aren't in a drag, e.g. when adding a new
   1394         // item to this layout via the customize drawer.
   1395         // Guard against that case.
   1396         if (mDragging) {
   1397             mDragging = false;
   1398 
   1399             // Fade out the drag indicators
   1400             if (mCrosshairsAnimator != null) {
   1401                 mCrosshairsAnimator.animateOut();
   1402             }
   1403         }
   1404 
   1405         // Invalidate the drag data
   1406         mDragCell[0] = -1;
   1407         mDragCell[1] = -1;
   1408         mDragOutlineAnims[mDragOutlineCurrent].animateOut();
   1409         mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
   1410 
   1411         setIsDragOverlapping(false);
   1412     }
   1413 
   1414     /**
   1415      * Mark a child as having been dropped.
   1416      * At the beginning of the drag operation, the child may have been on another
   1417      * screen, but it is re-parented before this method is called.
   1418      *
   1419      * @param child The child that is being dropped
   1420      */
   1421     void onDropChild(View child) {
   1422         if (child != null) {
   1423             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   1424             lp.dropped = true;
   1425             child.requestLayout();
   1426         }
   1427     }
   1428 
   1429     /**
   1430      * Computes a bounding rectangle for a range of cells
   1431      *
   1432      * @param cellX X coordinate of upper left corner expressed as a cell position
   1433      * @param cellY Y coordinate of upper left corner expressed as a cell position
   1434      * @param cellHSpan Width in cells
   1435      * @param cellVSpan Height in cells
   1436      * @param resultRect Rect into which to put the results
   1437      */
   1438     public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) {
   1439         final int cellWidth = mCellWidth;
   1440         final int cellHeight = mCellHeight;
   1441         final int widthGap = mWidthGap;
   1442         final int heightGap = mHeightGap;
   1443 
   1444         final int hStartPadding = getPaddingLeft();
   1445         final int vStartPadding = getPaddingTop();
   1446 
   1447         int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
   1448         int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
   1449 
   1450         int x = hStartPadding + cellX * (cellWidth + widthGap);
   1451         int y = vStartPadding + cellY * (cellHeight + heightGap);
   1452 
   1453         resultRect.set(x, y, x + width, y + height);
   1454     }
   1455 
   1456     /**
   1457      * Computes the required horizontal and vertical cell spans to always
   1458      * fit the given rectangle.
   1459      *
   1460      * @param width Width in pixels
   1461      * @param height Height in pixels
   1462      * @param result An array of length 2 in which to store the result (may be null).
   1463      */
   1464     public int[] rectToCell(int width, int height, int[] result) {
   1465         return rectToCell(getResources(), width, height, result);
   1466     }
   1467 
   1468     public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
   1469         // Always assume we're working with the smallest span to make sure we
   1470         // reserve enough space in both orientations.
   1471         int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
   1472         int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
   1473         int smallerSize = Math.min(actualWidth, actualHeight);
   1474 
   1475         // Always round up to next largest cell
   1476         int spanX = (int) Math.ceil(width / (float) smallerSize);
   1477         int spanY = (int) Math.ceil(height / (float) smallerSize);
   1478 
   1479         if (result == null) {
   1480             return new int[] { spanX, spanY };
   1481         }
   1482         result[0] = spanX;
   1483         result[1] = spanY;
   1484         return result;
   1485     }
   1486 
   1487     public int[] cellSpansToSize(int hSpans, int vSpans) {
   1488         int[] size = new int[2];
   1489         size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
   1490         size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
   1491         return size;
   1492     }
   1493 
   1494     /**
   1495      * Calculate the grid spans needed to fit given item
   1496      */
   1497     public void calculateSpans(ItemInfo info) {
   1498         final int minWidth;
   1499         final int minHeight;
   1500 
   1501         if (info instanceof LauncherAppWidgetInfo) {
   1502             minWidth = ((LauncherAppWidgetInfo) info).minWidth;
   1503             minHeight = ((LauncherAppWidgetInfo) info).minHeight;
   1504         } else if (info instanceof PendingAddWidgetInfo) {
   1505             minWidth = ((PendingAddWidgetInfo) info).minWidth;
   1506             minHeight = ((PendingAddWidgetInfo) info).minHeight;
   1507         } else {
   1508             // It's not a widget, so it must be 1x1
   1509             info.spanX = info.spanY = 1;
   1510             return;
   1511         }
   1512         int[] spans = rectToCell(minWidth, minHeight, null);
   1513         info.spanX = spans[0];
   1514         info.spanY = spans[1];
   1515     }
   1516 
   1517     /**
   1518      * Find the first vacant cell, if there is one.
   1519      *
   1520      * @param vacant Holds the x and y coordinate of the vacant cell
   1521      * @param spanX Horizontal cell span.
   1522      * @param spanY Vertical cell span.
   1523      *
   1524      * @return True if a vacant cell was found
   1525      */
   1526     public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
   1527 
   1528         return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
   1529     }
   1530 
   1531     static boolean findVacantCell(int[] vacant, int spanX, int spanY,
   1532             int xCount, int yCount, boolean[][] occupied) {
   1533 
   1534         for (int y = 0; y < yCount; y++) {
   1535             for (int x = 0; x < xCount; x++) {
   1536                 boolean available = !occupied[x][y];
   1537 out:            for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
   1538                     for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
   1539                         available = available && !occupied[i][j];
   1540                         if (!available) break out;
   1541                     }
   1542                 }
   1543 
   1544                 if (available) {
   1545                     vacant[0] = x;
   1546                     vacant[1] = y;
   1547                     return true;
   1548                 }
   1549             }
   1550         }
   1551 
   1552         return false;
   1553     }
   1554 
   1555     private void clearOccupiedCells() {
   1556         for (int x = 0; x < mCountX; x++) {
   1557             for (int y = 0; y < mCountY; y++) {
   1558                 mOccupied[x][y] = false;
   1559             }
   1560         }
   1561     }
   1562 
   1563     /**
   1564      * Given a view, determines how much that view can be expanded in all directions, in terms of
   1565      * whether or not there are other items occupying adjacent cells. Used by the
   1566      * AppWidgetResizeFrame to determine how the widget can be resized.
   1567      */
   1568     public void getExpandabilityArrayForView(View view, int[] expandability) {
   1569         final LayoutParams lp = (LayoutParams) view.getLayoutParams();
   1570         boolean flag;
   1571 
   1572         expandability[AppWidgetResizeFrame.LEFT] = 0;
   1573         for (int x = lp.cellX - 1; x >= 0; x--) {
   1574             flag = false;
   1575             for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan; y++) {
   1576                 if (mOccupied[x][y]) flag = true;
   1577             }
   1578             if (flag) break;
   1579             expandability[AppWidgetResizeFrame.LEFT]++;
   1580         }
   1581 
   1582         expandability[AppWidgetResizeFrame.TOP] = 0;
   1583         for (int y = lp.cellY - 1; y >= 0; y--) {
   1584             flag = false;
   1585             for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan; x++) {
   1586                 if (mOccupied[x][y]) flag = true;
   1587             }
   1588             if (flag) break;
   1589             expandability[AppWidgetResizeFrame.TOP]++;
   1590         }
   1591 
   1592         expandability[AppWidgetResizeFrame.RIGHT] = 0;
   1593         for (int x = lp.cellX + lp.cellHSpan; x < mCountX; x++) {
   1594             flag = false;
   1595             for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan; y++) {
   1596                 if (mOccupied[x][y]) flag = true;
   1597             }
   1598             if (flag) break;
   1599             expandability[AppWidgetResizeFrame.RIGHT]++;
   1600         }
   1601 
   1602         expandability[AppWidgetResizeFrame.BOTTOM] = 0;
   1603         for (int y = lp.cellY + lp.cellVSpan; y < mCountY; y++) {
   1604             flag = false;
   1605             for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan; x++) {
   1606                 if (mOccupied[x][y]) flag = true;
   1607             }
   1608             if (flag) break;
   1609             expandability[AppWidgetResizeFrame.BOTTOM]++;
   1610         }
   1611     }
   1612 
   1613     public void onMove(View view, int newCellX, int newCellY) {
   1614         LayoutParams lp = (LayoutParams) view.getLayoutParams();
   1615         markCellsAsUnoccupiedForView(view);
   1616         markCellsForView(newCellX, newCellY, lp.cellHSpan, lp.cellVSpan, true);
   1617     }
   1618 
   1619     public void markCellsAsOccupiedForView(View view) {
   1620         if (view == null || view.getParent() != mChildren) return;
   1621         LayoutParams lp = (LayoutParams) view.getLayoutParams();
   1622         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
   1623     }
   1624 
   1625     public void markCellsAsUnoccupiedForView(View view) {
   1626         if (view == null || view.getParent() != mChildren) return;
   1627         LayoutParams lp = (LayoutParams) view.getLayoutParams();
   1628         markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
   1629     }
   1630 
   1631     private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean value) {
   1632         for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
   1633             for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
   1634                 mOccupied[x][y] = value;
   1635             }
   1636         }
   1637     }
   1638 
   1639     public int getDesiredWidth() {
   1640         return mPaddingLeft + mPaddingRight + (mCountX * mCellWidth) +
   1641                 (Math.max((mCountX - 1), 0) * mWidthGap);
   1642     }
   1643 
   1644     public int getDesiredHeight()  {
   1645         return mPaddingTop + mPaddingBottom + (mCountY * mCellHeight) +
   1646                 (Math.max((mCountY - 1), 0) * mHeightGap);
   1647     }
   1648 
   1649     public boolean isOccupied(int x, int y) {
   1650         if (x < mCountX && y < mCountY) {
   1651             return mOccupied[x][y];
   1652         } else {
   1653             throw new RuntimeException("Position exceeds the bound of this CellLayout");
   1654         }
   1655     }
   1656 
   1657     @Override
   1658     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
   1659         return new CellLayout.LayoutParams(getContext(), attrs);
   1660     }
   1661 
   1662     @Override
   1663     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
   1664         return p instanceof CellLayout.LayoutParams;
   1665     }
   1666 
   1667     @Override
   1668     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
   1669         return new CellLayout.LayoutParams(p);
   1670     }
   1671 
   1672     public static class CellLayoutAnimationController extends LayoutAnimationController {
   1673         public CellLayoutAnimationController(Animation animation, float delay) {
   1674             super(animation, delay);
   1675         }
   1676 
   1677         @Override
   1678         protected long getDelayForView(View view) {
   1679             return (int) (Math.random() * 150);
   1680         }
   1681     }
   1682 
   1683     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
   1684         /**
   1685          * Horizontal location of the item in the grid.
   1686          */
   1687         @ViewDebug.ExportedProperty
   1688         public int cellX;
   1689 
   1690         /**
   1691          * Vertical location of the item in the grid.
   1692          */
   1693         @ViewDebug.ExportedProperty
   1694         public int cellY;
   1695 
   1696         /**
   1697          * Number of cells spanned horizontally by the item.
   1698          */
   1699         @ViewDebug.ExportedProperty
   1700         public int cellHSpan;
   1701 
   1702         /**
   1703          * Number of cells spanned vertically by the item.
   1704          */
   1705         @ViewDebug.ExportedProperty
   1706         public int cellVSpan;
   1707 
   1708         /**
   1709          * Indicates whether the item will set its x, y, width and height parameters freely,
   1710          * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
   1711          */
   1712         public boolean isLockedToGrid = true;
   1713 
   1714         // X coordinate of the view in the layout.
   1715         @ViewDebug.ExportedProperty
   1716         int x;
   1717         // Y coordinate of the view in the layout.
   1718         @ViewDebug.ExportedProperty
   1719         int y;
   1720 
   1721         boolean dropped;
   1722 
   1723         public LayoutParams(Context c, AttributeSet attrs) {
   1724             super(c, attrs);
   1725             cellHSpan = 1;
   1726             cellVSpan = 1;
   1727         }
   1728 
   1729         public LayoutParams(ViewGroup.LayoutParams source) {
   1730             super(source);
   1731             cellHSpan = 1;
   1732             cellVSpan = 1;
   1733         }
   1734 
   1735         public LayoutParams(LayoutParams source) {
   1736             super(source);
   1737             this.cellX = source.cellX;
   1738             this.cellY = source.cellY;
   1739             this.cellHSpan = source.cellHSpan;
   1740             this.cellVSpan = source.cellVSpan;
   1741         }
   1742 
   1743         public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
   1744             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
   1745             this.cellX = cellX;
   1746             this.cellY = cellY;
   1747             this.cellHSpan = cellHSpan;
   1748             this.cellVSpan = cellVSpan;
   1749         }
   1750 
   1751         public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap) {
   1752             if (isLockedToGrid) {
   1753                 final int myCellHSpan = cellHSpan;
   1754                 final int myCellVSpan = cellVSpan;
   1755                 final int myCellX = cellX;
   1756                 final int myCellY = cellY;
   1757 
   1758                 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
   1759                         leftMargin - rightMargin;
   1760                 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
   1761                         topMargin - bottomMargin;
   1762                 x = myCellX * (cellWidth + widthGap) + leftMargin;
   1763                 y = myCellY * (cellHeight + heightGap) + topMargin;
   1764             }
   1765         }
   1766 
   1767         public String toString() {
   1768             return "(" + this.cellX + ", " + this.cellY + ")";
   1769         }
   1770 
   1771         public void setWidth(int width) {
   1772             this.width = width;
   1773         }
   1774 
   1775         public int getWidth() {
   1776             return width;
   1777         }
   1778 
   1779         public void setHeight(int height) {
   1780             this.height = height;
   1781         }
   1782 
   1783         public int getHeight() {
   1784             return height;
   1785         }
   1786 
   1787         public void setX(int x) {
   1788             this.x = x;
   1789         }
   1790 
   1791         public int getX() {
   1792             return x;
   1793         }
   1794 
   1795         public void setY(int y) {
   1796             this.y = y;
   1797         }
   1798 
   1799         public int getY() {
   1800             return y;
   1801         }
   1802     }
   1803 
   1804     // This class stores info for two purposes:
   1805     // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
   1806     //    its spanX, spanY, and the screen it is on
   1807     // 2. When long clicking on an empty cell in a CellLayout, we save information about the
   1808     //    cellX and cellY coordinates and which page was clicked. We then set this as a tag on
   1809     //    the CellLayout that was long clicked
   1810     static final class CellInfo {
   1811         View cell;
   1812         int cellX = -1;
   1813         int cellY = -1;
   1814         int spanX;
   1815         int spanY;
   1816         int screen;
   1817         long container;
   1818 
   1819         @Override
   1820         public String toString() {
   1821             return "Cell[view=" + (cell == null ? "null" : cell.getClass())
   1822                     + ", x=" + cellX + ", y=" + cellY + "]";
   1823         }
   1824     }
   1825 
   1826     public boolean lastDownOnOccupiedCell() {
   1827         return mLastDownOnOccupiedCell;
   1828     }
   1829 }
   1830