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