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