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