Home | History | Annotate | Download | only in launcher2
      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 
     68     public CellLayout(Context context) {
     69         this(context, null);
     70     }
     71 
     72     public CellLayout(Context context, AttributeSet attrs) {
     73         this(context, attrs, 0);
     74     }
     75 
     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
    111     public void dispatchDraw(Canvas canvas) {
    112         super.dispatchDraw(canvas);
    113     }
    114 
    115     @Override
    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 
    127     int getCountX() {
    128         return mPortrait ? mShortAxisCells : mLongAxisCells;
    129     }
    130 
    131     int getCountY() {
    132         return mPortrait ? mLongAxisCells : mShortAxisCells;
    133     }
    134 
    135     @Override
    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
    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
    156     protected void onAttachedToWindow() {
    157         super.onAttachedToWindow();
    158         mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
    159     }
    160 
    161     @Override
    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 
    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 
    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 
    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      */
    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      */
    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      */
    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 
    447     int getCellWidth() {
    448         return mCellWidth;
    449     }
    450 
    451     int getCellHeight() {
    452         return mCellHeight;
    453     }
    454 
    455     int getLeftPadding() {
    456         return mPortrait ? mShortAxisStartPadding : mLongAxisStartPadding;
    457     }
    458 
    459     int getTopPadding() {
    460         return mPortrait ? mLongAxisStartPadding : mShortAxisStartPadding;
    461     }
    462 
    463     int getRightPadding() {
    464         return mPortrait ? mShortAxisEndPadding : mLongAxisEndPadding;
    465     }
    466 
    467     int getBottomPadding() {
    468         return mPortrait ? mLongAxisEndPadding : mShortAxisEndPadding;
    469     }
    470 
    471     @Override
    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
    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
    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
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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 
    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
    841     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
    842         return new CellLayout.LayoutParams(getContext(), attrs);
    843     }
    844 
    845     @Override
    846     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    847         return p instanceof CellLayout.LayoutParams;
    848     }
    849 
    850     @Override
    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 
    896         public LayoutParams(Context c, AttributeSet attrs) {
    897             super(c, attrs);
    898             cellHSpan = 1;
    899             cellVSpan = 1;
    900         }
    901 
    902         public LayoutParams(ViewGroup.LayoutParams source) {
    903             super(source);
    904             cellHSpan = 1;
    905             cellVSpan = 1;
    906         }
    907 
    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 
    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 
    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 
    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
    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 
   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 
   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          */
   1045         boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
   1046             return findCellForSpan(cellXY, spanX, spanY, true);
   1047         }
   1048 
   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
   1089         public String toString() {
   1090             return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
   1091                     ", y=" + cellY + "]";
   1092         }
   1093     }
   1094 
   1095     public boolean lastDownOnOccupiedCell() {
   1096         return mLastDownOnOccupiedCell;
   1097     }
   1098 }
   1099 
   1100 
   1101