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