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