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