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