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