CellLayout.java revision 0280c3be4d9f8fc6fdf015b7ecd276eb26f76f2d
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher2;
18
19import com.android.launcher.R;
20
21import android.app.WallpaperManager;
22import android.content.Context;
23import android.content.res.Resources;
24import android.content.res.TypedArray;
25import android.graphics.Canvas;
26import android.graphics.Rect;
27import android.graphics.RectF;
28import android.graphics.drawable.Drawable;
29import android.util.AttributeSet;
30import android.view.ContextMenu;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.ViewDebug;
34import android.view.ViewGroup;
35import android.view.animation.Animation;
36import android.view.animation.LayoutAnimationController;
37
38import java.util.Arrays;
39
40public class CellLayout extends ViewGroup {
41    static final String TAG = "CellLayout";
42
43    private int mCellWidth;
44    private int mCellHeight;
45
46    private int mLeftPadding;
47    private int mRightPadding;
48    private int mTopPadding;
49    private int mBottomPadding;
50
51    private int mCountX;
52    private int mCountY;
53
54    private int mWidthGap;
55    private int mHeightGap;
56
57    private final Rect mRect = new Rect();
58    private final RectF mRectF = new RectF();
59    private final CellInfo mCellInfo = new CellInfo();
60
61    // This is a temporary variable to prevent having to allocate a new object just to
62    // return an (x, y) value from helper functions. Do NOT use it to maintain other state.
63    private final int[] mTmpCellXY = new int[2];
64
65    boolean[][] mOccupied;
66
67    private OnTouchListener mInterceptTouchListener;
68
69    private float mBackgroundAlpha;
70    private final Rect mBackgroundLayoutRect = new Rect();
71    private Drawable mBackground;
72    private Drawable mBackgroundHover;
73    // If we're actively dragging something over this screen and it's small,
74    // mHover is true
75    private boolean mHover = false;
76
77    private final RectF mDragRect = new RectF();
78
79    // When dragging, used to indicate a vacant drop location
80    private Drawable mVacantDrawable;
81
82    // When dragging, used to indicate an occupied drop location
83    private Drawable mOccupiedDrawable;
84
85    // Updated to point to mVacantDrawable or mOccupiedDrawable, as appropriate
86    private Drawable mDragRectDrawable;
87
88    // When a drag operation is in progress, holds the nearest cell to the touch point
89    private final int[] mDragCell = new int[2];
90
91    private final WallpaperManager mWallpaperManager;
92
93    public CellLayout(Context context) {
94        this(context, null);
95    }
96
97    public CellLayout(Context context, AttributeSet attrs) {
98        this(context, attrs, 0);
99    }
100
101    public CellLayout(Context context, AttributeSet attrs, int defStyle) {
102        super(context, attrs, defStyle);
103
104        // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
105        // the user where a dragged item will land when dropped.
106        setWillNotDraw(false);
107        mVacantDrawable = getResources().getDrawable(R.drawable.rounded_rect_green);
108        mOccupiedDrawable = getResources().getDrawable(R.drawable.rounded_rect_red);
109
110        if (LauncherApplication.isScreenXLarge()) {
111            mBackground = getResources().getDrawable(R.drawable.mini_home_screen_bg);
112            mBackground.setFilterBitmap(true);
113            mBackgroundHover = getResources().getDrawable(R.drawable.mini_home_screen_bg_hover);
114            mBackgroundHover.setFilterBitmap(true);
115        }
116
117        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
118
119        mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
120        mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
121
122        mLeftPadding =
123            a.getDimensionPixelSize(R.styleable.CellLayout_xAxisStartPadding, 10);
124        mRightPadding =
125            a.getDimensionPixelSize(R.styleable.CellLayout_xAxisEndPadding, 10);
126        mTopPadding =
127            a.getDimensionPixelSize(R.styleable.CellLayout_yAxisStartPadding, 10);
128        mBottomPadding =
129            a.getDimensionPixelSize(R.styleable.CellLayout_yAxisEndPadding, 10);
130
131        mCountX = LauncherModel.getCellCountX();
132        mCountY = LauncherModel.getCellCountY();
133        mOccupied = new boolean[mCountX][mCountY];
134
135        a.recycle();
136
137        setAlwaysDrawnWithCacheEnabled(false);
138
139        mWallpaperManager = WallpaperManager.getInstance(getContext());
140    }
141
142    public void setHover(boolean value) {
143        if (mHover != value) {
144            invalidate();
145        }
146        mHover = value;
147    }
148
149    @Override
150    public void dispatchDraw(Canvas canvas) {
151        if (mBackgroundAlpha > 0.0f) {
152            final Drawable bg = mHover ? mBackgroundHover : mBackground;
153            bg.setAlpha((int) (mBackgroundAlpha * 255));
154            bg.draw(canvas);
155        }
156        super.dispatchDraw(canvas);
157    }
158
159    @Override
160    protected void onDraw(Canvas canvas) {
161        if (!mDragRect.isEmpty()) {
162            mDragRectDrawable.setBounds(
163                    (int)mDragRect.left,
164                    (int)mDragRect.top,
165                    (int)mDragRect.right,
166                    (int)mDragRect.bottom);
167            mDragRectDrawable.draw(canvas);
168        }
169        super.onDraw(canvas);
170    }
171
172    @Override
173    public void cancelLongPress() {
174        super.cancelLongPress();
175
176        // Cancel long press for all children
177        final int count = getChildCount();
178        for (int i = 0; i < count; i++) {
179            final View child = getChildAt(i);
180            child.cancelLongPress();
181        }
182    }
183
184    public void setOnInterceptTouchListener(View.OnTouchListener listener) {
185        mInterceptTouchListener = listener;
186    }
187
188    int getCountX() {
189        return mCountX;
190    }
191
192    int getCountY() {
193        return mCountY;
194    }
195
196    // Takes canonical layout parameters
197    public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params) {
198        final LayoutParams lp = params;
199
200        // Generate an id for each view, this assumes we have at most 256x256 cells
201        // per workspace screen
202        if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
203            // If the horizontal or vertical span is set to -1, it is taken to
204            // mean that it spans the extent of the CellLayout
205            if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
206            if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
207
208            child.setId(childId);
209
210            // We might be in the middle or end of shrinking/fading to a dimmed view
211            // Make sure this view's alpha is set the same as all the rest of the views
212            child.setAlpha(getAlpha());
213            addView(child, index, lp);
214
215            markCellsAsOccupiedForView(child);
216
217            return true;
218        }
219        return false;
220    }
221
222    @Override
223    public void removeAllViews() {
224        clearOccupiedCells();
225    }
226
227    @Override
228    public void removeAllViewsInLayout() {
229        clearOccupiedCells();
230    }
231
232    @Override
233    public void removeView(View view) {
234        markCellsAsUnoccupiedForView(view);
235        super.removeView(view);
236    }
237
238    @Override
239    public void removeViewAt(int index) {
240        markCellsAsUnoccupiedForView(getChildAt(index));
241        super.removeViewAt(index);
242    }
243
244    @Override
245    public void removeViewInLayout(View view) {
246        markCellsAsUnoccupiedForView(view);
247        super.removeViewInLayout(view);
248    }
249
250    @Override
251    public void removeViews(int start, int count) {
252        for (int i = start; i < start + count; i++) {
253            markCellsAsUnoccupiedForView(getChildAt(i));
254        }
255        super.removeViews(start, count);
256    }
257
258    @Override
259    public void removeViewsInLayout(int start, int count) {
260        for (int i = start; i < start + count; i++) {
261            markCellsAsUnoccupiedForView(getChildAt(i));
262        }
263        super.removeViewsInLayout(start, count);
264    }
265
266    @Override
267    public void requestChildFocus(View child, View focused) {
268        super.requestChildFocus(child, focused);
269        if (child != null) {
270            Rect r = new Rect();
271            child.getDrawingRect(r);
272            requestRectangleOnScreen(r);
273        }
274    }
275
276    @Override
277    protected void onAttachedToWindow() {
278        super.onAttachedToWindow();
279        mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
280    }
281
282    public void setTagToCellInfoForPoint(int touchX, int touchY) {
283        final CellInfo cellInfo = mCellInfo;
284        final Rect frame = mRect;
285        final int x = touchX + mScrollX;
286        final int y = touchY + mScrollY;
287        final int count = getChildCount();
288
289        boolean found = false;
290        for (int i = count - 1; i >= 0; i--) {
291            final View child = getChildAt(i);
292
293            if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
294                child.getHitRect(frame);
295                if (frame.contains(x, y)) {
296                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
297                    cellInfo.cell = child;
298                    cellInfo.cellX = lp.cellX;
299                    cellInfo.cellY = lp.cellY;
300                    cellInfo.spanX = lp.cellHSpan;
301                    cellInfo.spanY = lp.cellVSpan;
302                    cellInfo.valid = true;
303                    found = true;
304                    break;
305                }
306            }
307        }
308
309        if (!found) {
310            final int cellXY[] = mTmpCellXY;
311            pointToCellExact(x, y, cellXY);
312
313            cellInfo.cell = null;
314            cellInfo.cellX = cellXY[0];
315            cellInfo.cellY = cellXY[1];
316            cellInfo.spanX = 1;
317            cellInfo.spanY = 1;
318            cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < mCountX &&
319                    cellXY[1] < mCountY && !mOccupied[cellXY[0]][cellXY[1]];
320        }
321        setTag(cellInfo);
322    }
323
324
325    @Override
326    public boolean onInterceptTouchEvent(MotionEvent ev) {
327        if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
328            return true;
329        }
330        final int action = ev.getAction();
331        final CellInfo cellInfo = mCellInfo;
332
333        if (action == MotionEvent.ACTION_DOWN) {
334            setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
335        } else if (action == MotionEvent.ACTION_UP) {
336            cellInfo.cell = null;
337            cellInfo.cellX = -1;
338            cellInfo.cellY = -1;
339            cellInfo.spanX = 0;
340            cellInfo.spanY = 0;
341            cellInfo.valid = false;
342            setTag(cellInfo);
343        }
344
345        return false;
346    }
347
348    @Override
349    public CellInfo getTag() {
350        return (CellInfo) super.getTag();
351    }
352
353    /**
354     * Check if the row 'y' is empty from columns 'left' to 'right', inclusive.
355     */
356    private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
357        for (int x = left; x <= right; x++) {
358            if (occupied[x][y]) {
359                return false;
360            }
361        }
362        return true;
363    }
364
365    /**
366     * Given a point, return the cell that strictly encloses that point
367     * @param x X coordinate of the point
368     * @param y Y coordinate of the point
369     * @param result Array of 2 ints to hold the x and y coordinate of the cell
370     */
371    void pointToCellExact(int x, int y, int[] result) {
372        final int hStartPadding = getLeftPadding();
373        final int vStartPadding = getTopPadding();
374
375        result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
376        result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
377
378        final int xAxis = mCountX;
379        final int yAxis = mCountY;
380
381        if (result[0] < 0) result[0] = 0;
382        if (result[0] >= xAxis) result[0] = xAxis - 1;
383        if (result[1] < 0) result[1] = 0;
384        if (result[1] >= yAxis) result[1] = yAxis - 1;
385    }
386
387    /**
388     * Given a point, return the cell that most closely encloses that point
389     * @param x X coordinate of the point
390     * @param y Y coordinate of the point
391     * @param result Array of 2 ints to hold the x and y coordinate of the cell
392     */
393    void pointToCellRounded(int x, int y, int[] result) {
394        pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
395    }
396
397    /**
398     * Given a cell coordinate, return the point that represents the upper left corner of that cell
399     *
400     * @param cellX X coordinate of the cell
401     * @param cellY Y coordinate of the cell
402     *
403     * @param result Array of 2 ints to hold the x and y coordinate of the point
404     */
405    void cellToPoint(int cellX, int cellY, int[] result) {
406        final int hStartPadding = getLeftPadding();
407        final int vStartPadding = getTopPadding();
408
409        result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
410        result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
411    }
412
413    int getCellWidth() {
414        return mCellWidth;
415    }
416
417    int getCellHeight() {
418        return mCellHeight;
419    }
420
421    int getLeftPadding() {
422        return mLeftPadding;
423    }
424
425    int getTopPadding() {
426        return mTopPadding;
427    }
428
429    int getRightPadding() {
430        return mRightPadding;
431    }
432
433    int getBottomPadding() {
434        return mBottomPadding;
435    }
436
437    @Override
438    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
439        // TODO: currently ignoring padding
440
441        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
442        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
443
444        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
445        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
446
447        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
448            throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
449        }
450
451        final int cellWidth = mCellWidth;
452        final int cellHeight = mCellHeight;
453
454        int numWidthGaps = mCountX - 1;
455        int numHeightGaps = mCountY - 1;
456
457        int vSpaceLeft = heightSpecSize - mTopPadding - mBottomPadding - (cellHeight * mCountY);
458        mHeightGap = vSpaceLeft / numHeightGaps;
459
460        int hSpaceLeft = widthSpecSize - mLeftPadding - mRightPadding - (cellWidth * mCountX);
461        mWidthGap = hSpaceLeft / numWidthGaps;
462
463        // center it around the min gaps
464        int minGap = Math.min(mWidthGap, mHeightGap);
465        mWidthGap = mHeightGap = minGap;
466
467        int count = getChildCount();
468
469        for (int i = 0; i < count; i++) {
470            View child = getChildAt(i);
471            LayoutParams lp = (LayoutParams) child.getLayoutParams();
472            lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap,
473                    mLeftPadding, mTopPadding);
474
475            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
476            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
477                    MeasureSpec.EXACTLY);
478
479            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
480        }
481        if (widthSpecMode == MeasureSpec.AT_MOST) {
482            int newWidth = mLeftPadding + mRightPadding + (mCountX * cellWidth) +
483                ((mCountX - 1) * minGap);
484            int newHeight = mTopPadding + mBottomPadding + (mCountY * cellHeight) +
485                ((mCountY - 1) * minGap);
486            setMeasuredDimension(newWidth, newHeight);
487        } else if (widthSpecMode == MeasureSpec.EXACTLY) {
488            setMeasuredDimension(widthSpecSize, heightSpecSize);
489        }
490    }
491
492    @Override
493    public void onLayout(boolean changed, int l, int t, int r, int b) {
494        int count = getChildCount();
495
496        for (int i = 0; i < count; i++) {
497            View child = getChildAt(i);
498            if (child.getVisibility() != GONE) {
499
500                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
501
502                int childLeft = lp.x;
503                int childTop = lp.y;
504                child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
505
506                if (lp.dropped) {
507                    lp.dropped = false;
508
509                    final int[] cellXY = mTmpCellXY;
510                    getLocationOnScreen(cellXY);
511                    mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop",
512                            cellXY[0] + childLeft + lp.width / 2,
513                            cellXY[1] + childTop + lp.height / 2, 0, null);
514                }
515            }
516        }
517    }
518
519    @Override
520    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
521        super.onSizeChanged(w, h, oldw, oldh);
522        mBackgroundLayoutRect.set(0, 0, w, h);
523        if (mBackground != null) {
524            mBackground.setBounds(mBackgroundLayoutRect);
525        }
526        if (mBackgroundHover != null) {
527            mBackgroundHover.setBounds(mBackgroundLayoutRect);
528        }
529    }
530
531    @Override
532    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
533        final int count = getChildCount();
534        for (int i = 0; i < count; i++) {
535            final View view = getChildAt(i);
536            view.setDrawingCacheEnabled(enabled);
537            // Update the drawing caches
538            view.buildDrawingCache(true);
539        }
540    }
541
542    @Override
543    protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
544        super.setChildrenDrawnWithCacheEnabled(enabled);
545    }
546
547    public float getBackgroundAlpha() {
548        return mBackgroundAlpha;
549    }
550
551    public void setBackgroundAlpha(float alpha) {
552        mBackgroundAlpha = alpha;
553        invalidate();
554    }
555
556    // Need to return true to let the view system know we know how to handle alpha-- this is
557    // because when our children have an alpha of 0.0f, they are still rendering their "dimmed"
558    // versions
559    @Override
560    protected boolean onSetAlpha(int alpha) {
561        return true;
562    }
563
564    public void setAlpha(float alpha) {
565        setChildrenAlpha(alpha);
566        super.setAlpha(alpha);
567    }
568
569    private void setChildrenAlpha(float alpha) {
570        final int childCount = getChildCount();
571        for (int i = 0; i < childCount; i++) {
572            getChildAt(i).setAlpha(alpha);
573        }
574    }
575
576    private boolean isVacantIgnoring(
577            int originX, int originY, int spanX, int spanY, View ignoreView) {
578        if (ignoreView != null) {
579            markCellsAsUnoccupiedForView(ignoreView);
580        }
581        for (int i = 0; i < spanY; i++) {
582            if (!isRowEmpty(originY + i, originX, originX + spanX - 1, mOccupied)) {
583                if (ignoreView != null) {
584                    markCellsAsOccupiedForView(ignoreView);
585                }
586                return false;
587            }
588        }
589        if (ignoreView != null) {
590            markCellsAsOccupiedForView(ignoreView);
591        }
592        return true;
593    }
594
595    private boolean isVacant(int originX, int originY, int spanX, int spanY) {
596        return isVacantIgnoring(originX, originY, spanX, spanY, null);
597    }
598
599    public View getChildAt(int x, int y) {
600        final int count = getChildCount();
601        for (int i = 0; i < count; i++) {
602            View child = getChildAt(i);
603            LayoutParams lp = (LayoutParams) child.getLayoutParams();
604
605            if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
606                    (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) {
607                return child;
608            }
609        }
610        return null;
611    }
612
613    /**
614     * Estimate the size that a child with the given dimensions will take in the layout.
615     */
616    void estimateChildSize(int minWidth, int minHeight, int[] result) {
617        // Assuming it's placed at 0, 0, find where the bottom right cell will land
618        rectToCell(minWidth, minHeight, result);
619
620        // Then figure out the rect it will occupy
621        cellToRect(0, 0, result[0], result[1], mRectF);
622        result[0] = (int)mRectF.width();
623        result[1] = (int)mRectF.height();
624    }
625
626    /**
627     * Estimate where the top left cell of the dragged item will land if it is dropped.
628     *
629     * @param originX The X value of the top left corner of the item
630     * @param originY The Y value of the top left corner of the item
631     * @param spanX The number of horizontal cells that the item spans
632     * @param spanY The number of vertical cells that the item spans
633     * @param result The estimated drop cell X and Y.
634     */
635    void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
636        final int countX = mCountX;
637        final int countY = mCountY;
638
639        // pointToCellRounded takes the top left of a cell but will pad that with
640        // cellWidth/2 and cellHeight/2 when finding the matching cell
641        pointToCellRounded(originX, originY, result);
642
643        // If the item isn't fully on this screen, snap to the edges
644        int rightOverhang = result[0] + spanX - countX;
645        if (rightOverhang > 0) {
646            result[0] -= rightOverhang; // Snap to right
647        }
648        result[0] = Math.max(0, result[0]); // Snap to left
649        int bottomOverhang = result[1] + spanY - countY;
650        if (bottomOverhang > 0) {
651            result[1] -= bottomOverhang; // Snap to bottom
652        }
653        result[1] = Math.max(0, result[1]); // Snap to top
654    }
655
656    void visualizeDropLocation(
657            View view, int originX, int originY, int spanX, int spanY, View draggedItem) {
658        final int[] originCell = mDragCell;
659        final int[] cellXY = mTmpCellXY;
660        estimateDropCell(originX, originY, spanX, spanY, cellXY);
661
662        // Only recalculate the bounding rect when necessary
663        if (!Arrays.equals(cellXY, originCell)) {
664            originCell[0] = cellXY[0];
665            originCell[1] = cellXY[1];
666
667            // Find the top left corner of the rect the object will occupy
668            final int[] topLeft = mTmpCellXY;
669            cellToPoint(originCell[0], originCell[1], topLeft);
670            final int left = topLeft[0];
671            final int top = topLeft[1];
672
673            // Now find the bottom right
674            final int[] bottomRight = mTmpCellXY;
675            cellToPoint(originCell[0] + spanX - 1, originCell[1] + spanY - 1, bottomRight);
676            bottomRight[0] += mCellWidth;
677            bottomRight[1] += mCellHeight;
678
679            boolean vacant =
680                isVacantIgnoring(originCell[0], originCell[1], spanX, spanY, draggedItem);
681            mDragRectDrawable = vacant ? mVacantDrawable : mOccupiedDrawable;
682
683            // mDragRect will be rendered in onDraw()
684            mDragRect.set(left, top, bottomRight[0], bottomRight[1]);
685            invalidate();
686        }
687    }
688
689    /**
690     * Find a vacant area that will fit the given bounds nearest the requested
691     * cell location. Uses Euclidean distance to score multiple vacant areas.
692     *
693     * @param pixelX The X location at which you want to search for a vacant area.
694     * @param pixelY The Y location at which you want to search for a vacant area.
695     * @param spanX Horizontal span of the object.
696     * @param spanY Vertical span of the object.
697     * @param vacantCells Pre-computed set of vacant cells to search.
698     * @param recycle Previously returned value to possibly recycle.
699     * @return The X, Y cell of a vacant area that can contain this object,
700     *         nearest the requested location.
701     */
702    int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] recycle) {
703
704        // Keep track of best-scoring drop area
705        final int[] bestXY = recycle != null ? recycle : new int[2];
706        double bestDistance = Double.MAX_VALUE;
707
708        for (int x = 0; x < mCountX - (spanX - 1); x++) {
709            inner:
710            for (int y = 0; y < mCountY - (spanY - 1); y++) {
711                for (int i = 0; i < spanX; i++) {
712                    for (int j = 0; j < spanY; j++) {
713                        if (mOccupied[x + i][y + j]) {
714                            // small optimization: we can skip to below the row we just found
715                            // an occupied cell
716                            y += j;
717                            continue inner;
718                        }
719                    }
720                }
721                final int[] cellXY = mTmpCellXY;
722                cellToPoint(x, y, cellXY);
723
724                double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
725                        + Math.pow(cellXY[1] - pixelY, 2));
726                if (distance <= bestDistance) {
727                    bestDistance = distance;
728                    bestXY[0] = x;
729                    bestXY[1] = y;
730                }
731            }
732        }
733
734        // Return null if no suitable location found
735        if (bestDistance < Double.MAX_VALUE) {
736            return bestXY;
737        } else {
738            return null;
739        }
740    }
741
742    boolean existsEmptyCell() {
743        return findCellForSpan(null, 1, 1);
744    }
745
746    /**
747     * Finds the upper-left coordinate of the first rectangle in the grid that can
748     * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
749     * then this method will only return coordinates for rectangles that contain the cell
750     * (intersectX, intersectY)
751     *
752     * @param cellXY The array that will contain the position of a vacant cell if such a cell
753     *               can be found.
754     * @param spanX The horizontal span of the cell we want to find.
755     * @param spanY The vertical span of the cell we want to find.
756     *
757     * @return True if a vacant cell of the specified dimension was found, false otherwise.
758     */
759    boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
760        return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null);
761    }
762
763    /**
764     * Like above, but ignores any cells occupied by the item "ignoreView"
765     *
766     * @param cellXY The array that will contain the position of a vacant cell if such a cell
767     *               can be found.
768     * @param spanX The horizontal span of the cell we want to find.
769     * @param spanY The vertical span of the cell we want to find.
770     * @param ignoreView The home screen item we should treat as not occupying any space
771     * @return
772     */
773    boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
774        return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, ignoreView);
775    }
776
777    /**
778     * Like above, but if intersectX and intersectY are not -1, then this method will try to
779     * return coordinates for rectangles that contain the cell [intersectX, intersectY]
780     *
781     * @param spanX The horizontal span of the cell we want to find.
782     * @param spanY The vertical span of the cell we want to find.
783     * @param ignoreView The home screen item we should treat as not occupying any space
784     * @param intersectX The X coordinate of the cell that we should try to overlap
785     * @param intersectX The Y coordinate of the cell that we should try to overlap
786     *
787     * @return True if a vacant cell of the specified dimension was found, false otherwise.
788     */
789    boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
790            int intersectX, int intersectY) {
791        return findCellForSpanThatIntersectsIgnoring(
792                cellXY, spanX, spanY, intersectX, intersectY, null);
793    }
794
795    /**
796     * The superset of the above two methods
797     */
798    boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
799            int intersectX, int intersectY, View ignoreView) {
800        if (ignoreView != null) {
801            markCellsAsUnoccupiedForView(ignoreView);
802        }
803
804        while (true) {
805            int startX = 0;
806            if (intersectX >= 0) {
807                startX = Math.max(startX, intersectX - (spanX - 1));
808            }
809            int endX = mCountX - (spanX - 1);
810            if (intersectX >= 0) {
811                endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
812            }
813            int startY = 0;
814            if (intersectY >= 0) {
815                startY = Math.max(startY, intersectY - (spanY - 1));
816            }
817            int endY = mCountY - (spanY - 1);
818            if (intersectY >= 0) {
819                endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
820            }
821
822            for (int x = startX; x < endX; x++) {
823                inner:
824                for (int y = startY; y < endY; y++) {
825                    for (int i = 0; i < spanX; i++) {
826                        for (int j = 0; j < spanY; j++) {
827                            if (mOccupied[x + i][y + j]) {
828                                // small optimization: we can skip to below the row we just found
829                                // an occupied cell
830                                y += j;
831                                continue inner;
832                            }
833                        }
834                    }
835                    if (cellXY != null) {
836                        cellXY[0] = x;
837                        cellXY[1] = y;
838                    }
839                    if (ignoreView != null) {
840                        markCellsAsOccupiedForView(ignoreView);
841                    }
842                    return true;
843                }
844            }
845            if (intersectX == -1 && intersectY == -1) {
846                break;
847            } else {
848                // if we failed to find anything, try again but without any requirements of
849                // intersecting
850                intersectX = -1;
851                intersectY = -1;
852                continue;
853            }
854        }
855
856        if (ignoreView != null) {
857            markCellsAsOccupiedForView(ignoreView);
858        }
859        return false;
860    }
861
862    /**
863     * Called when drag has left this CellLayout or has been completed (successfully or not)
864     */
865    void onDragExit() {
866        // Invalidate the drag data
867        mDragCell[0] = -1;
868        mDragCell[1] = -1;
869
870        setHover(false);
871        mDragRect.setEmpty();
872        invalidate();
873    }
874
875    /**
876     * Mark a child as having been dropped.
877     *
878     * @param child The child that is being dropped
879     */
880    void onDropChild(View child) {
881        if (child != null) {
882            LayoutParams lp = (LayoutParams) child.getLayoutParams();
883            lp.isDragging = false;
884            lp.dropped = true;
885            mDragRect.setEmpty();
886            child.requestLayout();
887        }
888        onDragExit();
889    }
890
891    void onDropAborted(View child) {
892        if (child != null) {
893            ((LayoutParams) child.getLayoutParams()).isDragging = false;
894        }
895        onDragExit();
896    }
897
898    /**
899     * Start dragging the specified child
900     *
901     * @param child The child that is being dragged
902     */
903    void onDragChild(View child) {
904        LayoutParams lp = (LayoutParams) child.getLayoutParams();
905        lp.isDragging = true;
906        mDragRect.setEmpty();
907    }
908
909    /**
910     * Computes a bounding rectangle for a range of cells
911     *
912     * @param cellX X coordinate of upper left corner expressed as a cell position
913     * @param cellY Y coordinate of upper left corner expressed as a cell position
914     * @param cellHSpan Width in cells
915     * @param cellVSpan Height in cells
916     * @param resultRect Rect into which to put the results
917     */
918    public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) {
919        final int cellWidth = mCellWidth;
920        final int cellHeight = mCellHeight;
921        final int widthGap = mWidthGap;
922        final int heightGap = mHeightGap;
923
924        final int hStartPadding = getLeftPadding();
925        final int vStartPadding = getTopPadding();
926
927        int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
928        int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
929
930        int x = hStartPadding + cellX * (cellWidth + widthGap);
931        int y = vStartPadding + cellY * (cellHeight + heightGap);
932
933        resultRect.set(x, y, x + width, y + height);
934    }
935
936    /**
937     * Computes the required horizontal and vertical cell spans to always
938     * fit the given rectangle.
939     *
940     * @param width Width in pixels
941     * @param height Height in pixels
942     * @param result An array of length 2 in which to store the result (may be null).
943     */
944    public int[] rectToCell(int width, int height, int[] result) {
945        // Always assume we're working with the smallest span to make sure we
946        // reserve enough space in both orientations.
947        final Resources resources = getResources();
948        int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
949        int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
950        int smallerSize = Math.min(actualWidth, actualHeight);
951
952        // Always round up to next largest cell
953        int spanX = (width + smallerSize) / smallerSize;
954        int spanY = (height + smallerSize) / smallerSize;
955
956        if (result == null) {
957            return new int[] { spanX, spanY };
958        }
959        result[0] = spanX;
960        result[1] = spanY;
961        return result;
962    }
963
964    /**
965     * Find the first vacant cell, if there is one.
966     *
967     * @param vacant Holds the x and y coordinate of the vacant cell
968     * @param spanX Horizontal cell span.
969     * @param spanY Vertical cell span.
970     *
971     * @return True if a vacant cell was found
972     */
973    public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
974
975        return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
976    }
977
978    static boolean findVacantCell(int[] vacant, int spanX, int spanY,
979            int xCount, int yCount, boolean[][] occupied) {
980
981        for (int x = 0; x < xCount; x++) {
982            for (int y = 0; y < yCount; y++) {
983                boolean available = !occupied[x][y];
984out:            for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
985                    for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
986                        available = available && !occupied[i][j];
987                        if (!available) break out;
988                    }
989                }
990
991                if (available) {
992                    vacant[0] = x;
993                    vacant[1] = y;
994                    return true;
995                }
996            }
997        }
998
999        return false;
1000    }
1001
1002    /**
1003     * Update the array of occupied cells (mOccupied), and return a flattened copy of the array.
1004     */
1005    boolean[] getOccupiedCellsFlattened() {
1006        final int xCount = mCountX;
1007        final int yCount = mCountY;
1008        final boolean[][] occupied = mOccupied;
1009
1010        final boolean[] flat = new boolean[xCount * yCount];
1011        for (int y = 0; y < yCount; y++) {
1012            for (int x = 0; x < xCount; x++) {
1013                flat[y * xCount + x] = occupied[x][y];
1014            }
1015        }
1016
1017        return flat;
1018    }
1019
1020    private void clearOccupiedCells() {
1021        for (int x = 0; x < mCountX; x++) {
1022            for (int y = 0; y < mCountY; y++) {
1023                mOccupied[x][y] = false;
1024            }
1025        }
1026    }
1027
1028    public void onMove(View view, int newCellX, int newCellY) {
1029        LayoutParams lp = (LayoutParams) view.getLayoutParams();
1030        markCellsAsUnoccupiedForView(view);
1031        markCellsForView(newCellX, newCellY, lp.cellHSpan, lp.cellVSpan, true);
1032    }
1033
1034    private void markCellsAsOccupiedForView(View view) {
1035        LayoutParams lp = (LayoutParams) view.getLayoutParams();
1036        markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
1037    }
1038
1039    private void markCellsAsUnoccupiedForView(View view) {
1040        LayoutParams lp = (LayoutParams) view.getLayoutParams();
1041        markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
1042    }
1043
1044    private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean value) {
1045        for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
1046            for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
1047                mOccupied[x][y] = value;
1048            }
1049        }
1050    }
1051
1052    @Override
1053    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1054        return new CellLayout.LayoutParams(getContext(), attrs);
1055    }
1056
1057    @Override
1058    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1059        return p instanceof CellLayout.LayoutParams;
1060    }
1061
1062    @Override
1063    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1064        return new CellLayout.LayoutParams(p);
1065    }
1066
1067    public static class CellLayoutAnimationController extends LayoutAnimationController {
1068        public CellLayoutAnimationController(Animation animation, float delay) {
1069            super(animation, delay);
1070        }
1071
1072        @Override
1073        protected long getDelayForView(View view) {
1074            return (int) (Math.random() * 150);
1075        }
1076    }
1077
1078    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1079        /**
1080         * Horizontal location of the item in the grid.
1081         */
1082        @ViewDebug.ExportedProperty
1083        public int cellX;
1084
1085        /**
1086         * Vertical location of the item in the grid.
1087         */
1088        @ViewDebug.ExportedProperty
1089        public int cellY;
1090
1091        /**
1092         * Number of cells spanned horizontally by the item.
1093         */
1094        @ViewDebug.ExportedProperty
1095        public int cellHSpan;
1096
1097        /**
1098         * Number of cells spanned vertically by the item.
1099         */
1100        @ViewDebug.ExportedProperty
1101        public int cellVSpan;
1102
1103        /**
1104         * Is this item currently being dragged
1105         */
1106        public boolean isDragging;
1107
1108        // X coordinate of the view in the layout.
1109        @ViewDebug.ExportedProperty
1110        int x;
1111        // Y coordinate of the view in the layout.
1112        @ViewDebug.ExportedProperty
1113        int y;
1114
1115        boolean dropped;
1116
1117        public LayoutParams(Context c, AttributeSet attrs) {
1118            super(c, attrs);
1119            cellHSpan = 1;
1120            cellVSpan = 1;
1121        }
1122
1123        public LayoutParams(ViewGroup.LayoutParams source) {
1124            super(source);
1125            cellHSpan = 1;
1126            cellVSpan = 1;
1127        }
1128
1129        public LayoutParams(LayoutParams source) {
1130            super(source);
1131            this.cellX = source.cellX;
1132            this.cellY = source.cellY;
1133            this.cellHSpan = source.cellHSpan;
1134            this.cellVSpan = source.cellVSpan;
1135        }
1136
1137        public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
1138            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
1139            this.cellX = cellX;
1140            this.cellY = cellY;
1141            this.cellHSpan = cellHSpan;
1142            this.cellVSpan = cellVSpan;
1143        }
1144
1145        public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
1146                int hStartPadding, int vStartPadding) {
1147
1148            final int myCellHSpan = cellHSpan;
1149            final int myCellVSpan = cellVSpan;
1150            final int myCellX = cellX;
1151            final int myCellY = cellY;
1152
1153            width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
1154                    leftMargin - rightMargin;
1155            height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
1156                    topMargin - bottomMargin;
1157
1158            x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
1159            y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
1160        }
1161
1162        public String toString() {
1163            return "(" + this.cellX + ", " + this.cellY + ")";
1164        }
1165    }
1166
1167    // This class stores info for two purposes:
1168    // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
1169    //    its spanX, spanY, and the screen it is on
1170    // 2. When long clicking on an empty cell in a CellLayout, we save information about the
1171    //    cellX and cellY coordinates and which page was clicked. We then set this as a tag on
1172    //    the CellLayout that was long clicked
1173    static final class CellInfo implements ContextMenu.ContextMenuInfo {
1174        View cell;
1175        int cellX = -1;
1176        int cellY = -1;
1177        int spanX;
1178        int spanY;
1179        int screen;
1180        boolean valid;
1181
1182        @Override
1183        public String toString() {
1184            return "Cell[view=" + (cell == null ? "null" : cell.getClass())
1185                    + ", x=" + cellX + ", y=" + cellY + "]";
1186        }
1187    }
1188}
1189