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