CellLayout.java revision a5902524d4403885eb4c50360bf3465c6be796ef
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher2;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Rect;
22import android.graphics.RectF;
23import android.util.AttributeSet;
24import android.view.ContextMenu;
25import android.view.MotionEvent;
26import android.view.View;
27import android.view.ViewDebug;
28import android.view.ViewGroup;
29
30import java.util.ArrayList;
31
32public class CellLayout extends ViewGroup {
33    private boolean mPortrait;
34
35    private int mCellWidth;
36    private int mCellHeight;
37
38    private int mLongAxisStartPadding;
39    private int mLongAxisEndPadding;
40
41    private int mShortAxisStartPadding;
42    private int mShortAxisEndPadding;
43
44    private int mShortAxisCells;
45    private int mLongAxisCells;
46
47    private int mWidthGap;
48    private int mHeightGap;
49
50    private final Rect mRect = new Rect();
51    private final CellInfo mCellInfo = new CellInfo();
52
53    int[] mCellXY = new int[2];
54
55    boolean[][] mOccupied;
56
57    private RectF mDragRect = new RectF();
58
59    private boolean mDirtyTag;
60
61    public CellLayout(Context context) {
62        this(context, null);
63    }
64
65    public CellLayout(Context context, AttributeSet attrs) {
66        this(context, attrs, 0);
67    }
68
69    public CellLayout(Context context, AttributeSet attrs, int defStyle) {
70        super(context, attrs, defStyle);
71        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
72
73        mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
74        mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
75
76        mLongAxisStartPadding =
77            a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10);
78        mLongAxisEndPadding =
79            a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10);
80        mShortAxisStartPadding =
81            a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10);
82        mShortAxisEndPadding =
83            a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10);
84
85        mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);
86        mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4);
87
88        a.recycle();
89
90        setAlwaysDrawnWithCacheEnabled(false);
91
92        if (mOccupied == null) {
93            if (mPortrait) {
94                mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
95            } else {
96                mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
97            }
98        }
99    }
100
101    @Override
102    public void cancelLongPress() {
103        super.cancelLongPress();
104
105        // Cancel long press for all children
106        final int count = getChildCount();
107        for (int i = 0; i < count; i++) {
108            final View child = getChildAt(i);
109            child.cancelLongPress();
110        }
111    }
112
113    int getCountX() {
114        return mPortrait ? mShortAxisCells : mLongAxisCells;
115    }
116
117    int getCountY() {
118        return mPortrait ? mLongAxisCells : mShortAxisCells;
119    }
120
121    @Override
122    public void addView(View child, int index, ViewGroup.LayoutParams params) {
123        // Generate an id for each view, this assumes we have at most 256x256 cells
124        // per workspace screen
125        final LayoutParams cellParams = (LayoutParams) params;
126        child.setId(((getId() & 0xFF) << 16) |
127                (cellParams.cellX & 0xFF) << 8 | (cellParams.cellY & 0xFF));
128
129        super.addView(child, index, params);
130    }
131
132    @Override
133    public void requestChildFocus(View child, View focused) {
134        super.requestChildFocus(child, focused);
135        if (child != null) {
136            Rect r = new Rect();
137            child.getDrawingRect(r);
138            requestRectangleOnScreen(r);
139        }
140    }
141
142    @Override
143    protected void onAttachedToWindow() {
144        super.onAttachedToWindow();
145        mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
146    }
147
148    @Override
149    public boolean onInterceptTouchEvent(MotionEvent ev) {
150        final int action = ev.getAction();
151        final CellInfo cellInfo = mCellInfo;
152
153        if (action == MotionEvent.ACTION_DOWN) {
154            final Rect frame = mRect;
155            final int x = (int) ev.getX() + mScrollX;
156            final int y = (int) ev.getY() + mScrollY;
157            final int count = getChildCount();
158
159            boolean found = false;
160            for (int i = count - 1; i >= 0; i--) {
161                final View child = getChildAt(i);
162
163                if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
164                    child.getHitRect(frame);
165                    if (frame.contains(x, y)) {
166                        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
167                        cellInfo.cell = child;
168                        cellInfo.cellX = lp.cellX;
169                        cellInfo.cellY = lp.cellY;
170                        cellInfo.spanX = lp.cellHSpan;
171                        cellInfo.spanY = lp.cellVSpan;
172                        cellInfo.valid = true;
173                        found = true;
174                        mDirtyTag = false;
175                        break;
176                    }
177                }
178            }
179
180            if (!found) {
181                int cellXY[] = mCellXY;
182                pointToCellExact(x, y, cellXY);
183
184                final boolean portrait = mPortrait;
185                final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
186                final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
187
188                final boolean[][] occupied = mOccupied;
189                findOccupiedCells(xCount, yCount, occupied, null);
190
191                cellInfo.cell = null;
192                cellInfo.cellX = cellXY[0];
193                cellInfo.cellY = cellXY[1];
194                cellInfo.spanX = 1;
195                cellInfo.spanY = 1;
196                cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
197                        cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
198
199                // Instead of finding the interesting vacant cells here, wait until a
200                // caller invokes getTag() to retrieve the result. Finding the vacant
201                // cells is a bit expensive and can generate many new objects, it's
202                // therefore better to defer it until we know we actually need it.
203
204                mDirtyTag = true;
205            }
206            setTag(cellInfo);
207        } else if (action == MotionEvent.ACTION_UP) {
208            cellInfo.cell = null;
209            cellInfo.cellX = -1;
210            cellInfo.cellY = -1;
211            cellInfo.spanX = 0;
212            cellInfo.spanY = 0;
213            cellInfo.valid = false;
214            mDirtyTag = false;
215            setTag(cellInfo);
216        }
217
218        return false;
219    }
220
221    @Override
222    public CellInfo getTag() {
223        final CellInfo info = (CellInfo) super.getTag();
224        if (mDirtyTag && info.valid) {
225            final boolean portrait = mPortrait;
226            final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
227            final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
228
229            final boolean[][] occupied = mOccupied;
230            findOccupiedCells(xCount, yCount, occupied, null);
231
232            findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
233
234            mDirtyTag = false;
235        }
236        return info;
237    }
238
239    private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y,
240            int xCount, int yCount, boolean[][] occupied) {
241
242        cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
243        cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
244        cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
245        cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
246        cellInfo.clearVacantCells();
247
248        if (occupied[x][y]) {
249            return;
250        }
251
252        cellInfo.current.set(x, y, x, y);
253
254        findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
255    }
256
257    private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
258            CellInfo cellInfo) {
259
260        addVacantCell(current, cellInfo);
261
262        if (current.left > 0) {
263            if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
264                current.left--;
265                findVacantCell(current, xCount, yCount, occupied, cellInfo);
266                current.left++;
267            }
268        }
269
270        if (current.right < xCount - 1) {
271            if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
272                current.right++;
273                findVacantCell(current, xCount, yCount, occupied, cellInfo);
274                current.right--;
275            }
276        }
277
278        if (current.top > 0) {
279            if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
280                current.top--;
281                findVacantCell(current, xCount, yCount, occupied, cellInfo);
282                current.top++;
283            }
284        }
285
286        if (current.bottom < yCount - 1) {
287            if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
288                current.bottom++;
289                findVacantCell(current, xCount, yCount, occupied, cellInfo);
290                current.bottom--;
291            }
292        }
293    }
294
295    private static void addVacantCell(Rect current, CellInfo cellInfo) {
296        CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();
297        cell.cellX = current.left;
298        cell.cellY = current.top;
299        cell.spanX = current.right - current.left + 1;
300        cell.spanY = current.bottom - current.top + 1;
301        if (cell.spanX > cellInfo.maxVacantSpanX) {
302            cellInfo.maxVacantSpanX = cell.spanX;
303            cellInfo.maxVacantSpanXSpanY = cell.spanY;
304        }
305        if (cell.spanY > cellInfo.maxVacantSpanY) {
306            cellInfo.maxVacantSpanY = cell.spanY;
307            cellInfo.maxVacantSpanYSpanX = cell.spanX;
308        }
309        cellInfo.vacantCells.add(cell);
310    }
311
312    private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
313        for (int y = top; y <= bottom; y++) {
314            if (occupied[x][y]) {
315                return false;
316            }
317        }
318        return true;
319    }
320
321    private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
322        for (int x = left; x <= right; x++) {
323            if (occupied[x][y]) {
324                return false;
325            }
326        }
327        return true;
328    }
329
330    CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
331        final boolean portrait = mPortrait;
332        final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
333        final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
334
335        boolean[][] occupied = mOccupied;
336
337        if (occupiedCells != null) {
338            for (int y = 0; y < yCount; y++) {
339                for (int x = 0; x < xCount; x++) {
340                    occupied[x][y] = occupiedCells[y * xCount + x];
341                }
342            }
343        } else {
344            findOccupiedCells(xCount, yCount, occupied, ignoreView);
345        }
346
347        CellInfo cellInfo = new CellInfo();
348
349        cellInfo.cellX = -1;
350        cellInfo.cellY = -1;
351        cellInfo.spanY = 0;
352        cellInfo.spanX = 0;
353        cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
354        cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
355        cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
356        cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
357        cellInfo.screen = mCellInfo.screen;
358
359        Rect current = cellInfo.current;
360
361        for (int x = 0; x < xCount; x++) {
362            for (int y = 0; y < yCount; y++) {
363                if (!occupied[x][y]) {
364                    current.set(x, y, x, y);
365                    findVacantCell(current, xCount, yCount, occupied, cellInfo);
366                    occupied[x][y] = true;
367                }
368            }
369        }
370
371        cellInfo.valid = cellInfo.vacantCells.size() > 0;
372
373        // Assume the caller will perform their own cell searching, otherwise we
374        // risk causing an unnecessary rebuild after findCellForSpan()
375
376        return cellInfo;
377    }
378
379    /**
380     * Given a point, return the cell that strictly encloses that point
381     * @param x X coordinate of the point
382     * @param y Y coordinate of the point
383     * @param result Array of 2 ints to hold the x and y coordinate of the cell
384     */
385    void pointToCellExact(int x, int y, int[] result) {
386        final boolean portrait = mPortrait;
387
388        final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
389        final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
390
391        result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
392        result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
393
394        final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
395        final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
396
397        if (result[0] < 0) result[0] = 0;
398        if (result[0] >= xAxis) result[0] = xAxis - 1;
399        if (result[1] < 0) result[1] = 0;
400        if (result[1] >= yAxis) result[1] = yAxis - 1;
401    }
402
403    /**
404     * Given a point, return the cell that most closely encloses that point
405     * @param x X coordinate of the point
406     * @param y Y coordinate of the point
407     * @param result Array of 2 ints to hold the x and y coordinate of the cell
408     */
409    void pointToCellRounded(int x, int y, int[] result) {
410        pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
411    }
412
413    /**
414     * Given a cell coordinate, return the point that represents the upper left corner of that cell
415     *
416     * @param cellX X coordinate of the cell
417     * @param cellY Y coordinate of the cell
418     *
419     * @param result Array of 2 ints to hold the x and y coordinate of the point
420     */
421    void cellToPoint(int cellX, int cellY, int[] result) {
422        final boolean portrait = mPortrait;
423
424        final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
425        final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
426
427
428        result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
429        result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
430    }
431
432    @Override
433    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
434        // TODO: currently ignoring padding
435
436        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
437        int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec);
438
439        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
440        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
441
442        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
443            throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
444        }
445
446        final int shortAxisCells = mShortAxisCells;
447        final int longAxisCells = mLongAxisCells;
448        final int longAxisStartPadding = mLongAxisStartPadding;
449        final int longAxisEndPadding = mLongAxisEndPadding;
450        final int shortAxisStartPadding = mShortAxisStartPadding;
451        final int shortAxisEndPadding = mShortAxisEndPadding;
452        final int cellWidth = mCellWidth;
453        final int cellHeight = mCellHeight;
454
455        mPortrait = heightSpecSize > widthSpecSize;
456
457        int numShortGaps = shortAxisCells - 1;
458        int numLongGaps = longAxisCells - 1;
459
460        if (mPortrait) {
461            int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding
462                    - (cellHeight * longAxisCells);
463            mHeightGap = vSpaceLeft / numLongGaps;
464
465            int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding
466                    - (cellWidth * shortAxisCells);
467            if (numShortGaps > 0) {
468                mWidthGap = hSpaceLeft / numShortGaps;
469            } else {
470                mWidthGap = 0;
471            }
472        } else {
473            int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding
474                    - (cellWidth * longAxisCells);
475            mWidthGap = hSpaceLeft / numLongGaps;
476
477            int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding
478                    - (cellHeight * shortAxisCells);
479            if (numShortGaps > 0) {
480                mHeightGap = vSpaceLeft / numShortGaps;
481            } else {
482                mHeightGap = 0;
483            }
484        }
485
486        int count = getChildCount();
487
488        for (int i = 0; i < count; i++) {
489            View child = getChildAt(i);
490            LayoutParams lp = (LayoutParams) child.getLayoutParams();
491
492            if (mPortrait) {
493                lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding,
494                        longAxisStartPadding);
495            } else {
496                lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding,
497                        shortAxisStartPadding);
498            }
499
500            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
501            int childheightMeasureSpec =
502                    MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
503            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
504        }
505
506        setMeasuredDimension(widthSpecSize, heightSpecSize);
507    }
508
509    @Override
510    protected void onLayout(boolean changed, int l, int t, int r, int b) {
511        int count = getChildCount();
512
513        for (int i = 0; i < count; i++) {
514            View child = getChildAt(i);
515            if (child.getVisibility() != GONE) {
516
517                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
518
519                int childLeft = lp.x;
520                int childTop = lp.y;
521                child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
522            }
523        }
524    }
525
526    @Override
527    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
528        final int count = getChildCount();
529        for (int i = 0; i < count; i++) {
530            final View view = getChildAt(i);
531            view.setDrawingCacheEnabled(enabled);
532            // Update the drawing caches
533            view.buildDrawingCache(true);
534        }
535    }
536
537    @Override
538    protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
539        super.setChildrenDrawnWithCacheEnabled(enabled);
540    }
541
542    /**
543     * Find a vacant area that will fit the given bounds nearest the requested
544     * cell location. Uses Euclidean distance to score multiple vacant areas.
545     *
546     * @param pixelX The X location at which you want to search for a vacant area.
547     * @param pixelY The Y location at which you want to search for a vacant area.
548     * @param spanX Horizontal span of the object.
549     * @param spanY Vertical span of the object.
550     * @param vacantCells Pre-computed set of vacant cells to search.
551     * @param recycle Previously returned value to possibly recycle.
552     * @return The X, Y cell of a vacant area that can contain this object,
553     *         nearest the requested location.
554     */
555    int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
556            CellInfo vacantCells, int[] recycle) {
557
558        // Keep track of best-scoring drop area
559        final int[] bestXY = recycle != null ? recycle : new int[2];
560        final int[] cellXY = mCellXY;
561        double bestDistance = Double.MAX_VALUE;
562
563        // Bail early if vacant cells aren't valid
564        if (!vacantCells.valid) {
565            return null;
566        }
567
568        // Look across all vacant cells for best fit
569        final int size = vacantCells.vacantCells.size();
570        for (int i = 0; i < size; i++) {
571            final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
572
573            // Reject if vacant cell isn't our exact size
574            if (cell.spanX != spanX || cell.spanY != spanY) {
575                continue;
576            }
577
578            // Score is center distance from requested pixel
579            cellToPoint(cell.cellX, cell.cellY, cellXY);
580
581            double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) +
582                    Math.pow(cellXY[1] - pixelY, 2));
583            if (distance <= bestDistance) {
584                bestDistance = distance;
585                bestXY[0] = cell.cellX;
586                bestXY[1] = cell.cellY;
587            }
588        }
589
590        // Return null if no suitable location found
591        if (bestDistance < Double.MAX_VALUE) {
592            return bestXY;
593        } else {
594            return null;
595        }
596    }
597
598    /**
599     * Drop a child at the specified position
600     *
601     * @param child The child that is being dropped
602     * @param targetXY Destination area to move to
603     */
604    void onDropChild(View child, int[] targetXY) {
605        LayoutParams lp = (LayoutParams) child.getLayoutParams();
606        lp.cellX = targetXY[0];
607        lp.cellY = targetXY[1];
608        lp.isDragging = false;
609        mDragRect.setEmpty();
610        child.requestLayout();
611        invalidate();
612    }
613
614    void onDropAborted(View child) {
615        if (child != null) {
616            ((LayoutParams) child.getLayoutParams()).isDragging = false;
617            invalidate();
618        }
619        mDragRect.setEmpty();
620    }
621
622    /**
623     * Start dragging the specified child
624     *
625     * @param child The child that is being dragged
626     */
627    void onDragChild(View child) {
628        LayoutParams lp = (LayoutParams) child.getLayoutParams();
629        lp.isDragging = true;
630        mDragRect.setEmpty();
631    }
632
633    /**
634     * Drag a child over the specified position
635     *
636     * @param child The child that is being dropped
637     * @param cellX The child's new x cell location
638     * @param cellY The child's new y cell location
639     */
640    void onDragOverChild(View child, int cellX, int cellY) {
641        int[] cellXY = mCellXY;
642        pointToCellRounded(cellX, cellY, cellXY);
643        LayoutParams lp = (LayoutParams) child.getLayoutParams();
644        cellToRect(cellXY[0], cellXY[1], lp.cellHSpan, lp.cellVSpan, mDragRect);
645        invalidate();
646    }
647
648    /**
649     * Computes a bounding rectangle for a range of cells
650     *
651     * @param cellX X coordinate of upper left corner expressed as a cell position
652     * @param cellY Y coordinate of upper left corner expressed as a cell position
653     * @param cellHSpan Width in cells
654     * @param cellVSpan Height in cells
655     * @param dragRect Rectnagle into which to put the results
656     */
657    public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF dragRect) {
658        final boolean portrait = mPortrait;
659        final int cellWidth = mCellWidth;
660        final int cellHeight = mCellHeight;
661        final int widthGap = mWidthGap;
662        final int heightGap = mHeightGap;
663
664        final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
665        final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
666
667        int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
668        int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
669
670        int x = hStartPadding + cellX * (cellWidth + widthGap);
671        int y = vStartPadding + cellY * (cellHeight + heightGap);
672
673        dragRect.set(x, y, x + width, y + height);
674    }
675
676    /**
677     * Computes the required horizontal and vertical cell spans to always
678     * fit the given rectangle.
679     *
680     * @param width Width in pixels
681     * @param height Height in pixels
682     */
683    public int[] rectToCell(int width, int height) {
684        // Always assume we're working with the smallest span to make sure we
685        // reserve enough space in both orientations.
686        int actualWidth = mCellWidth + mWidthGap;
687        int actualHeight = mCellHeight + mHeightGap;
688        int smallerSize = Math.min(actualWidth, actualHeight);
689
690        // Always round up to next largest cell
691        int spanX = (width + smallerSize) / smallerSize;
692        int spanY = (height + smallerSize) / smallerSize;
693        return new int[] { spanX, spanY };
694    }
695
696    /**
697     * Find the first vacant cell, if there is one.
698     *
699     * @param vacant Holds the x and y coordinate of the vacant cell
700     * @param spanX Horizontal cell span.
701     * @param spanY Vertical cell span.
702     *
703     * @return True if a vacant cell was found
704     */
705    public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
706        final boolean portrait = mPortrait;
707        final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
708        final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
709        final boolean[][] occupied = mOccupied;
710
711        findOccupiedCells(xCount, yCount, occupied, null);
712
713        return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
714    }
715
716    static boolean findVacantCell(int[] vacant, int spanX, int spanY,
717            int xCount, int yCount, boolean[][] occupied) {
718
719        for (int x = 0; x < xCount; x++) {
720            for (int y = 0; y < yCount; y++) {
721                boolean available = !occupied[x][y];
722out:            for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
723                    for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
724                        available = available && !occupied[i][j];
725                        if (!available) break out;
726                    }
727                }
728
729                if (available) {
730                    vacant[0] = x;
731                    vacant[1] = y;
732                    return true;
733                }
734            }
735        }
736
737        return false;
738    }
739
740    boolean[] getOccupiedCells() {
741        final boolean portrait = mPortrait;
742        final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
743        final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
744        final boolean[][] occupied = mOccupied;
745
746        findOccupiedCells(xCount, yCount, occupied, null);
747
748        final boolean[] flat = new boolean[xCount * yCount];
749        for (int y = 0; y < yCount; y++) {
750            for (int x = 0; x < xCount; x++) {
751                flat[y * xCount + x] = occupied[x][y];
752            }
753        }
754
755        return flat;
756    }
757
758    private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied, View ignoreView) {
759        for (int x = 0; x < xCount; x++) {
760            for (int y = 0; y < yCount; y++) {
761                occupied[x][y] = false;
762            }
763        }
764
765        int count = getChildCount();
766        for (int i = 0; i < count; i++) {
767            View child = getChildAt(i);
768            if (child instanceof Folder || child.equals(ignoreView)) {
769                continue;
770            }
771            LayoutParams lp = (LayoutParams) child.getLayoutParams();
772
773            for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
774                for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
775                    occupied[x][y] = true;
776                }
777            }
778        }
779    }
780
781    @Override
782    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
783        return new CellLayout.LayoutParams(getContext(), attrs);
784    }
785
786    @Override
787    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
788        return p instanceof CellLayout.LayoutParams;
789    }
790
791    @Override
792    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
793        return new CellLayout.LayoutParams(p);
794    }
795
796    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
797        /**
798         * Horizontal location of the item in the grid.
799         */
800        @ViewDebug.ExportedProperty
801        public int cellX;
802
803        /**
804         * Vertical location of the item in the grid.
805         */
806        @ViewDebug.ExportedProperty
807        public int cellY;
808
809        /**
810         * Number of cells spanned horizontally by the item.
811         */
812        @ViewDebug.ExportedProperty
813        public int cellHSpan;
814
815        /**
816         * Number of cells spanned vertically by the item.
817         */
818        @ViewDebug.ExportedProperty
819        public int cellVSpan;
820
821        /**
822         * Is this item currently being dragged
823         */
824        public boolean isDragging;
825
826        // X coordinate of the view in the layout.
827        @ViewDebug.ExportedProperty
828        int x;
829        // Y coordinate of the view in the layout.
830        @ViewDebug.ExportedProperty
831        int y;
832
833        public LayoutParams(Context c, AttributeSet attrs) {
834            super(c, attrs);
835            cellHSpan = 1;
836            cellVSpan = 1;
837        }
838
839        public LayoutParams(ViewGroup.LayoutParams source) {
840            super(source);
841            cellHSpan = 1;
842            cellVSpan = 1;
843        }
844
845        public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
846            super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
847            this.cellX = cellX;
848            this.cellY = cellY;
849            this.cellHSpan = cellHSpan;
850            this.cellVSpan = cellVSpan;
851        }
852
853        public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
854                int hStartPadding, int vStartPadding) {
855
856            final int myCellHSpan = cellHSpan;
857            final int myCellVSpan = cellVSpan;
858            final int myCellX = cellX;
859            final int myCellY = cellY;
860
861            width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
862                    leftMargin - rightMargin;
863            height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
864                    topMargin - bottomMargin;
865
866            x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
867            y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
868        }
869    }
870
871    static final class CellInfo implements ContextMenu.ContextMenuInfo {
872        /**
873         * See View.AttachInfo.InvalidateInfo for futher explanations about
874         * the recycling mechanism. In this case, we recycle the vacant cells
875         * instances because up to several hundreds can be instanciated when
876         * the user long presses an empty cell.
877         */
878        static final class VacantCell {
879            int cellX;
880            int cellY;
881            int spanX;
882            int spanY;
883
884            // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
885            // like a reasonable compromise given the size of a VacantCell and
886            // the fact that the user is not likely to touch an empty 4x4 grid
887            // very often
888            private static final int POOL_LIMIT = 100;
889            private static final Object sLock = new Object();
890
891            private static int sAcquiredCount = 0;
892            private static VacantCell sRoot;
893
894            private VacantCell next;
895
896            static VacantCell acquire() {
897                synchronized (sLock) {
898                    if (sRoot == null) {
899                        return new VacantCell();
900                    }
901
902                    VacantCell info = sRoot;
903                    sRoot = info.next;
904                    sAcquiredCount--;
905
906                    return info;
907                }
908            }
909
910            void release() {
911                synchronized (sLock) {
912                    if (sAcquiredCount < POOL_LIMIT) {
913                        sAcquiredCount++;
914                        next = sRoot;
915                        sRoot = this;
916                    }
917                }
918            }
919
920            @Override
921            public String toString() {
922                return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
923                        ", spanY=" + spanY + "]";
924            }
925        }
926
927        View cell;
928        int cellX;
929        int cellY;
930        int spanX;
931        int spanY;
932        int screen;
933        boolean valid;
934
935        final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
936        int maxVacantSpanX;
937        int maxVacantSpanXSpanY;
938        int maxVacantSpanY;
939        int maxVacantSpanYSpanX;
940        final Rect current = new Rect();
941
942        void clearVacantCells() {
943            final ArrayList<VacantCell> list = vacantCells;
944            final int count = list.size();
945
946            for (int i = 0; i < count; i++) list.get(i).release();
947
948            list.clear();
949        }
950
951        void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
952            if (cellX < 0 || cellY < 0) {
953                maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
954                maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
955                clearVacantCells();
956                return;
957            }
958
959            final boolean[][] unflattened = new boolean[xCount][yCount];
960            for (int y = 0; y < yCount; y++) {
961                for (int x = 0; x < xCount; x++) {
962                    unflattened[x][y] = occupied[y * xCount + x];
963                }
964            }
965            CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
966        }
967
968        /**
969         * This method can be called only once! Calling #findVacantCellsFromOccupied will
970         * restore the ability to call this method.
971         *
972         * Finds the upper-left coordinate of the first rectangle in the grid that can
973         * hold a cell of the specified dimensions.
974         *
975         * @param cellXY The array that will contain the position of a vacant cell if such a cell
976         *               can be found.
977         * @param spanX The horizontal span of the cell we want to find.
978         * @param spanY The vertical span of the cell we want to find.
979         *
980         * @return True if a vacant cell of the specified dimension was found, false otherwise.
981         */
982        boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
983            return findCellForSpan(cellXY, spanX, spanY, true);
984        }
985
986        boolean findCellForSpan(int[] cellXY, int spanX, int spanY, boolean clear) {
987            final ArrayList<VacantCell> list = vacantCells;
988            final int count = list.size();
989
990            boolean found = false;
991
992            if (this.spanX >= spanX && this.spanY >= spanY) {
993                cellXY[0] = cellX;
994                cellXY[1] = cellY;
995                found = true;
996            }
997
998            // Look for an exact match first
999            for (int i = 0; i < count; i++) {
1000                VacantCell cell = list.get(i);
1001                if (cell.spanX == spanX && cell.spanY == spanY) {
1002                    cellXY[0] = cell.cellX;
1003                    cellXY[1] = cell.cellY;
1004                    found = true;
1005                    break;
1006                }
1007            }
1008
1009            // Look for the first cell large enough
1010            for (int i = 0; i < count; i++) {
1011                VacantCell cell = list.get(i);
1012                if (cell.spanX >= spanX && cell.spanY >= spanY) {
1013                    cellXY[0] = cell.cellX;
1014                    cellXY[1] = cell.cellY;
1015                    found = true;
1016                    break;
1017                }
1018            }
1019
1020            if (clear) clearVacantCells();
1021
1022            return found;
1023        }
1024
1025        @Override
1026        public String toString() {
1027            return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
1028                    ", y=" + cellY + "]";
1029        }
1030    }
1031}
1032
1033
1034