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