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