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