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