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