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