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