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