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