Refactoring out grid occupancy management in a separate class

Change-Id: I37a830c0f2eb0a0dd4f5fc78fa29127cb18cb3c2
This commit is contained in:
Sunny Goyal
2016-04-02 14:12:34 -07:00
parent 3c4b03dd01
commit ff4ba2d995
8 changed files with 392 additions and 379 deletions
+126 -243
View File
@@ -32,7 +32,6 @@ import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.TransitionDrawable; import android.graphics.drawable.TransitionDrawable;
import android.os.Build; import android.os.Build;
import android.os.Parcelable; import android.os.Parcelable;
@@ -55,6 +54,8 @@ import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.util.CellAndSpan;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.ParcelableSparseArray; import com.android.launcher3.util.ParcelableSparseArray;
import com.android.launcher3.util.Thunk; import com.android.launcher3.util.Thunk;
@@ -81,9 +82,9 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
private int mFixedCellHeight; private int mFixedCellHeight;
@ViewDebug.ExportedProperty(category = "launcher") @ViewDebug.ExportedProperty(category = "launcher")
@Thunk int mCountX; private int mCountX;
@ViewDebug.ExportedProperty(category = "launcher") @ViewDebug.ExportedProperty(category = "launcher")
@Thunk int mCountY; private int mCountY;
private int mOriginalWidthGap; private int mOriginalWidthGap;
private int mOriginalHeightGap; private int mOriginalHeightGap;
@@ -101,8 +102,8 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
@Thunk final int[] mTmpPoint = new int[2]; @Thunk final int[] mTmpPoint = new int[2];
@Thunk final int[] mTempLocation = new int[2]; @Thunk final int[] mTempLocation = new int[2];
boolean[][] mOccupied; private GridOccupancy mOccupied;
boolean[][] mTmpOccupied; private GridOccupancy mTmpOccupied;
private OnTouchListener mInterceptTouchListener; private OnTouchListener mInterceptTouchListener;
private StylusEventHelper mStylusEventHelper; private StylusEventHelper mStylusEventHelper;
@@ -203,10 +204,12 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
mWidthGap = mOriginalWidthGap = 0; mWidthGap = mOriginalWidthGap = 0;
mHeightGap = mOriginalHeightGap = 0; mHeightGap = mOriginalHeightGap = 0;
mMaxGap = Integer.MAX_VALUE; mMaxGap = Integer.MAX_VALUE;
mCountX = (int) grid.inv.numColumns;
mCountY = (int) grid.inv.numRows; mCountX = grid.inv.numColumns;
mOccupied = new boolean[mCountX][mCountY]; mCountY = grid.inv.numRows;
mTmpOccupied = new boolean[mCountX][mCountY]; mOccupied = new GridOccupancy(mCountX, mCountY);
mTmpOccupied = new GridOccupancy(mCountX, mCountY);
mPreviousReorderDirection[0] = INVALID_DIRECTION; mPreviousReorderDirection[0] = INVALID_DIRECTION;
mPreviousReorderDirection[1] = INVALID_DIRECTION; mPreviousReorderDirection[1] = INVALID_DIRECTION;
@@ -375,8 +378,8 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
public void setGridSize(int x, int y) { public void setGridSize(int x, int y) {
mCountX = x; mCountX = x;
mCountY = y; mCountY = y;
mOccupied = new boolean[mCountX][mCountY]; mOccupied = new GridOccupancy(mCountX, mCountY);
mTmpOccupied = new boolean[mCountX][mCountY]; mTmpOccupied = new GridOccupancy(mCountX, mCountY);
mTempRectStack.clear(); mTempRectStack.clear();
mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap, mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
mCountX, mCountY); mCountX, mCountY);
@@ -494,7 +497,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
cd.setBounds(0, 0, mCellWidth, mCellHeight); cd.setBounds(0, 0, mCellWidth, mCellHeight);
for (int i = 0; i < mCountX; i++) { for (int i = 0; i < mCountX; i++) {
for (int j = 0; j < mCountY; j++) { for (int j = 0; j < mCountY; j++) {
if (mOccupied[i][j]) { if (mOccupied.cells[i][j]) {
cellToPoint(i, j, pt); cellToPoint(i, j, pt);
canvas.save(); canvas.save();
canvas.translate(pt[0], pt[1]); canvas.translate(pt[0], pt[1]);
@@ -655,14 +658,14 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
@Override @Override
public void removeAllViews() { public void removeAllViews() {
clearOccupiedCells(); mOccupied.clear();
mShortcutsAndWidgets.removeAllViews(); mShortcutsAndWidgets.removeAllViews();
} }
@Override @Override
public void removeAllViewsInLayout() { public void removeAllViewsInLayout() {
if (mShortcutsAndWidgets.getChildCount() > 0) { if (mShortcutsAndWidgets.getChildCount() > 0) {
clearOccupiedCells(); mOccupied.clear();
mShortcutsAndWidgets.removeAllViewsInLayout(); mShortcutsAndWidgets.removeAllViewsInLayout();
} }
} }
@@ -963,10 +966,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration, public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
int delay, boolean permanent, boolean adjustOccupied) { int delay, boolean permanent, boolean adjustOccupied) {
ShortcutAndWidgetContainer clc = getShortcutsAndWidgets(); ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
boolean[][] occupied = mOccupied;
if (!permanent) {
occupied = mTmpOccupied;
}
if (clc.indexOfChild(child) != -1) { if (clc.indexOfChild(child) != -1) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final LayoutParams lp = (LayoutParams) child.getLayoutParams();
@@ -981,8 +980,9 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
final int oldX = lp.x; final int oldX = lp.x;
final int oldY = lp.y; final int oldY = lp.y;
if (adjustOccupied) { if (adjustOccupied) {
occupied[lp.cellX][lp.cellY] = false; GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
occupied[cellX][cellY] = true; occupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
} }
lp.isLockedToGrid = true; lp.isLockedToGrid = true;
if (permanent) { if (permanent) {
@@ -1218,7 +1218,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
// First, let's see if this thing fits anywhere // First, let's see if this thing fits anywhere
for (int i = 0; i < minSpanX; i++) { for (int i = 0; i < minSpanX; i++) {
for (int j = 0; j < minSpanY; j++) { for (int j = 0; j < minSpanY; j++) {
if (mOccupied[x + i][y + j]) { if (mOccupied.cells[x + i][y + j]) {
continue inner; continue inner;
} }
} }
@@ -1235,7 +1235,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
while (!(hitMaxX && hitMaxY)) { while (!(hitMaxX && hitMaxY)) {
if (incX && !hitMaxX) { if (incX && !hitMaxX) {
for (int j = 0; j < ySize; j++) { for (int j = 0; j < ySize; j++) {
if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) { if (x + xSize > countX -1 || mOccupied.cells[x + xSize][y + j]) {
// We can't move out horizontally // We can't move out horizontally
hitMaxX = true; hitMaxX = true;
} }
@@ -1245,7 +1245,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
} }
} else if (!hitMaxY) { } else if (!hitMaxY) {
for (int i = 0; i < xSize; i++) { for (int i = 0; i < xSize; i++) {
if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) { if (y + ySize > countY - 1 || mOccupied.cells[x + i][y + ySize]) {
// We can't move out vertically // We can't move out vertically
hitMaxY = true; hitMaxY = true;
} }
@@ -1303,7 +1303,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
return bestXY; return bestXY;
} }
/** /**
* Find a vacant area that will fit the given bounds nearest the requested * Find a vacant area that will fit the given bounds nearest the requested
* cell location, and will also weigh in a suggested direction vector of the * cell location, and will also weigh in a suggested direction vector of the
* desired location. This method computers distance based on unit grid distances, * desired location. This method computers distance based on unit grid distances,
@@ -1377,17 +1377,18 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
int[] direction, ItemConfiguration currentState) { int[] direction, ItemConfiguration currentState) {
CellAndSpan c = currentState.map.get(v); CellAndSpan c = currentState.map.get(v);
boolean success = false; boolean success = false;
markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); mTmpOccupied.markCells(c, false);
markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true); mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation); findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, direction,
mTmpOccupied.cells, null, mTempLocation);
if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) { if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
c.x = mTempLocation[0]; c.cellX = mTempLocation[0];
c.y = mTempLocation[1]; c.cellY = mTempLocation[1];
success = true; success = true;
} }
markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); mTmpOccupied.markCells(c, true);
return success; return success;
} }
@@ -1440,32 +1441,32 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
CellAndSpan cs = config.map.get(views.get(i)); CellAndSpan cs = config.map.get(views.get(i));
switch (which) { switch (which) {
case LEFT: case LEFT:
int left = cs.x; int left = cs.cellX;
for (int j = cs.y; j < cs.y + cs.spanY; j++) { for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
if (left < leftEdge[j] || leftEdge[j] < 0) { if (left < leftEdge[j] || leftEdge[j] < 0) {
leftEdge[j] = left; leftEdge[j] = left;
} }
} }
break; break;
case RIGHT: case RIGHT:
int right = cs.x + cs.spanX; int right = cs.cellX + cs.spanX;
for (int j = cs.y; j < cs.y + cs.spanY; j++) { for (int j = cs.cellY; j < cs.cellY + cs.spanY; j++) {
if (right > rightEdge[j]) { if (right > rightEdge[j]) {
rightEdge[j] = right; rightEdge[j] = right;
} }
} }
break; break;
case TOP: case TOP:
int top = cs.y; int top = cs.cellY;
for (int j = cs.x; j < cs.x + cs.spanX; j++) { for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
if (top < topEdge[j] || topEdge[j] < 0) { if (top < topEdge[j] || topEdge[j] < 0) {
topEdge[j] = top; topEdge[j] = top;
} }
} }
break; break;
case BOTTOM: case BOTTOM:
int bottom = cs.y + cs.spanY; int bottom = cs.cellY + cs.spanY;
for (int j = cs.x; j < cs.x + cs.spanX; j++) { for (int j = cs.cellX; j < cs.cellX + cs.spanX; j++) {
if (bottom > bottomEdge[j]) { if (bottom > bottomEdge[j]) {
bottomEdge[j] = bottom; bottomEdge[j] = bottom;
} }
@@ -1485,29 +1486,29 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
switch (whichEdge) { switch (whichEdge) {
case LEFT: case LEFT:
for (int i = cs.y; i < cs.y + cs.spanY; i++) { for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
if (leftEdge[i] == cs.x + cs.spanX) { if (leftEdge[i] == cs.cellX + cs.spanX) {
return true; return true;
} }
} }
break; break;
case RIGHT: case RIGHT:
for (int i = cs.y; i < cs.y + cs.spanY; i++) { for (int i = cs.cellY; i < cs.cellY + cs.spanY; i++) {
if (rightEdge[i] == cs.x) { if (rightEdge[i] == cs.cellX) {
return true; return true;
} }
} }
break; break;
case TOP: case TOP:
for (int i = cs.x; i < cs.x + cs.spanX; i++) { for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
if (topEdge[i] == cs.y + cs.spanY) { if (topEdge[i] == cs.cellY + cs.spanY) {
return true; return true;
} }
} }
break; break;
case BOTTOM: case BOTTOM:
for (int i = cs.x; i < cs.x + cs.spanX; i++) { for (int i = cs.cellX; i < cs.cellX + cs.spanX; i++) {
if (bottomEdge[i] == cs.y) { if (bottomEdge[i] == cs.cellY) {
return true; return true;
} }
} }
@@ -1521,17 +1522,17 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
CellAndSpan c = config.map.get(v); CellAndSpan c = config.map.get(v);
switch (whichEdge) { switch (whichEdge) {
case LEFT: case LEFT:
c.x -= delta; c.cellX -= delta;
break; break;
case RIGHT: case RIGHT:
c.x += delta; c.cellX += delta;
break; break;
case TOP: case TOP:
c.y -= delta; c.cellY -= delta;
break; break;
case BOTTOM: case BOTTOM:
default: default:
c.y += delta; c.cellY += delta;
break; break;
} }
} }
@@ -1545,16 +1546,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
public Rect getBoundingRect() { public Rect getBoundingRect() {
if (boundingRectDirty) { if (boundingRectDirty) {
boolean first = true; config.getBoundingRectForViews(views, boundingRect);
for (View v: views) {
CellAndSpan c = config.map.get(v);
if (first) {
boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
first = false;
} else {
boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
}
}
} }
return boundingRect; return boundingRect;
} }
@@ -1567,14 +1559,14 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
CellAndSpan r = config.map.get(right); CellAndSpan r = config.map.get(right);
switch (whichEdge) { switch (whichEdge) {
case LEFT: case LEFT:
return (r.x + r.spanX) - (l.x + l.spanX); return (r.cellX + r.spanX) - (l.cellX + l.spanX);
case RIGHT: case RIGHT:
return l.x - r.x; return l.cellX - r.cellX;
case TOP: case TOP:
return (r.y + r.spanY) - (l.y + l.spanY); return (r.cellY + r.spanY) - (l.cellY + l.spanY);
case BOTTOM: case BOTTOM:
default: default:
return l.y - r.y; return l.cellY - r.cellY;
} }
} }
} }
@@ -1618,7 +1610,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
// Mark the occupied state as false for the group of views we want to move. // Mark the occupied state as false for the group of views we want to move.
for (View v: views) { for (View v: views) {
CellAndSpan c = currentState.map.get(v); CellAndSpan c = currentState.map.get(v);
markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); mTmpOccupied.markCells(c, false);
} }
// We save the current configuration -- if we fail to find a solution we will revert // We save the current configuration -- if we fail to find a solution we will revert
@@ -1648,7 +1640,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
CellAndSpan c = currentState.map.get(v); CellAndSpan c = currentState.map.get(v);
// Adding view to cluster, mark it as not occupied. // Adding view to cluster, mark it as not occupied.
markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); mTmpOccupied.markCells(c, false);
} }
} }
} }
@@ -1674,7 +1666,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
// In either case, we set the occupied array as marked for the location of the views // In either case, we set the occupied array as marked for the location of the views
for (View v: cluster.views) { for (View v: cluster.views) {
CellAndSpan c = currentState.map.get(v); CellAndSpan c = currentState.map.get(v);
markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); mTmpOccupied.markCells(c, true);
} }
return foundSolution; return foundSolution;
@@ -1685,37 +1677,31 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
if (views.size() == 0) return true; if (views.size() == 0) return true;
boolean success = false; boolean success = false;
Rect boundingRect = null; Rect boundingRect = new Rect();
// We construct a rect which represents the entire group of views passed in // We construct a rect which represents the entire group of views passed in
for (View v: views) { currentState.getBoundingRectForViews(views, boundingRect);
CellAndSpan c = currentState.map.get(v);
if (boundingRect == null) {
boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
} else {
boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
}
}
// Mark the occupied state as false for the group of views we want to move. // Mark the occupied state as false for the group of views we want to move.
for (View v: views) { for (View v: views) {
CellAndSpan c = currentState.map.get(v); CellAndSpan c = currentState.map.get(v);
markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); mTmpOccupied.markCells(c, false);
} }
boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()]; GridOccupancy blockOccupied = new GridOccupancy(boundingRect.width(), boundingRect.height());
int top = boundingRect.top; int top = boundingRect.top;
int left = boundingRect.left; int left = boundingRect.left;
// We mark more precisely which parts of the bounding rect are truly occupied, allowing // We mark more precisely which parts of the bounding rect are truly occupied, allowing
// for interlocking. // for interlocking.
for (View v: views) { for (View v: views) {
CellAndSpan c = currentState.map.get(v); CellAndSpan c = currentState.map.get(v);
markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true); blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true);
} }
markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true); mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(), findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation); boundingRect.height(), direction,
mTmpOccupied.cells, blockOccupied.cells, mTempLocation);
// If we successfuly found a location by pushing the block of views, we commit it // If we successfuly found a location by pushing the block of views, we commit it
if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) { if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
@@ -1723,8 +1709,8 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
int deltaY = mTempLocation[1] - boundingRect.top; int deltaY = mTempLocation[1] - boundingRect.top;
for (View v: views) { for (View v: views) {
CellAndSpan c = currentState.map.get(v); CellAndSpan c = currentState.map.get(v);
c.x += deltaX; c.cellX += deltaX;
c.y += deltaY; c.cellY += deltaY;
} }
success = true; success = true;
} }
@@ -1732,15 +1718,11 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
// In either case, we set the occupied array as marked for the location of the views // In either case, we set the occupied array as marked for the location of the views
for (View v: views) { for (View v: views) {
CellAndSpan c = currentState.map.get(v); CellAndSpan c = currentState.map.get(v);
markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); mTmpOccupied.markCells(c, true);
} }
return success; return success;
} }
private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
}
// This method tries to find a reordering solution which satisfies the push mechanic by trying // This method tries to find a reordering solution which satisfies the push mechanic by trying
// to push items in each of the cardinal directions, in an order based on the direction vector // to push items in each of the cardinal directions, in an order based on the direction vector
// passed. // passed.
@@ -1850,8 +1832,8 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
if (ignoreView != null) { if (ignoreView != null) {
CellAndSpan c = solution.map.get(ignoreView); CellAndSpan c = solution.map.get(ignoreView);
if (c != null) { if (c != null) {
c.x = cellX; c.cellX = cellX;
c.y = cellY; c.cellY = cellY;
} }
} }
Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY); Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
@@ -1860,7 +1842,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
if (child == ignoreView) continue; if (child == ignoreView) continue;
CellAndSpan c = solution.map.get(child); CellAndSpan c = solution.map.get(child);
LayoutParams lp = (LayoutParams) child.getLayoutParams(); LayoutParams lp = (LayoutParams) child.getLayoutParams();
r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY); r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
if (Rect.intersects(r0, r1)) { if (Rect.intersects(r0, r1)) {
if (!lp.canReorder) { if (!lp.canReorder) {
return false; return false;
@@ -1911,14 +1893,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
} }
} }
private void copyOccupiedArray(boolean[][] occupied) {
for (int i = 0; i < mCountX; i++) {
for (int j = 0; j < mCountY; j++) {
occupied[i][j] = mOccupied[i][j];
}
}
}
private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY, private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
int spanX, int spanY, int[] direction, View dragView, boolean decX, int spanX, int spanY, int[] direction, View dragView, boolean decX,
ItemConfiguration solution) { ItemConfiguration solution) {
@@ -1926,7 +1900,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
copyCurrentStateToSolution(solution, false); copyCurrentStateToSolution(solution, false);
// Copy the current occupied array into the temporary occupied array. This array will be // Copy the current occupied array into the temporary occupied array. This array will be
// manipulated as necessary to find a solution. // manipulated as necessary to find a solution.
copyOccupiedArray(mTmpOccupied); mOccupied.copyTo(mTmpOccupied);
// We find the nearest cell into which we would place the dragged item, assuming there's // We find the nearest cell into which we would place the dragged item, assuming there's
// nothing in its way. // nothing in its way.
@@ -1952,10 +1926,10 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
solution.isSolution = false; solution.isSolution = false;
} else { } else {
solution.isSolution = true; solution.isSolution = true;
solution.dragViewX = result[0]; solution.cellX = result[0];
solution.dragViewY = result[1]; solution.cellY = result[1];
solution.dragViewSpanX = spanX; solution.spanX = spanX;
solution.dragViewSpanY = spanY; solution.spanY = spanY;
} }
return solution; return solution;
} }
@@ -1976,11 +1950,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
} }
private void copySolutionToTempState(ItemConfiguration solution, View dragView) { private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
for (int i = 0; i < mCountX; i++) { mTmpOccupied.clear();
for (int j = 0; j < mCountY; j++) {
mTmpOccupied[i][j] = false;
}
}
int childCount = mShortcutsAndWidgets.getChildCount(); int childCount = mShortcutsAndWidgets.getChildCount();
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
@@ -1989,26 +1959,21 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
LayoutParams lp = (LayoutParams) child.getLayoutParams(); LayoutParams lp = (LayoutParams) child.getLayoutParams();
CellAndSpan c = solution.map.get(child); CellAndSpan c = solution.map.get(child);
if (c != null) { if (c != null) {
lp.tmpCellX = c.x; lp.tmpCellX = c.cellX;
lp.tmpCellY = c.y; lp.tmpCellY = c.cellY;
lp.cellHSpan = c.spanX; lp.cellHSpan = c.spanX;
lp.cellVSpan = c.spanY; lp.cellVSpan = c.spanY;
markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); mTmpOccupied.markCells(c, true);
} }
} }
markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX, mTmpOccupied.markCells(solution, true);
solution.dragViewSpanY, mTmpOccupied, true);
} }
private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
commitDragView) { commitDragView) {
boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied; GridOccupancy occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
for (int i = 0; i < mCountX; i++) { occupied.clear();
for (int j = 0; j < mCountY; j++) {
occupied[i][j] = false;
}
}
int childCount = mShortcutsAndWidgets.getChildCount(); int childCount = mShortcutsAndWidgets.getChildCount();
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
@@ -2016,14 +1981,13 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
if (child == dragView) continue; if (child == dragView) continue;
CellAndSpan c = solution.map.get(child); CellAndSpan c = solution.map.get(child);
if (c != null) { if (c != null) {
animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0, animateChildToPosition(child, c.cellX, c.cellY, REORDER_ANIMATION_DURATION, 0,
DESTRUCTIVE_REORDER, false); DESTRUCTIVE_REORDER, false);
markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true); occupied.markCells(c, true);
} }
} }
if (commitDragView) { if (commitDragView) {
markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX, occupied.markCells(solution, true);
solution.dragViewSpanY, occupied, true);
} }
} }
@@ -2042,7 +2006,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
LayoutParams lp = (LayoutParams) child.getLayoutParams(); LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (c != null && !skip) { if (c != null && !skip) {
ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX, ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
lp.cellY, c.x, c.y, c.spanX, c.spanY); lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
rha.animate(); rha.animate();
} }
} }
@@ -2186,11 +2150,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
} }
private void commitTempPlacement() { private void commitTempPlacement() {
for (int i = 0; i < mCountX; i++) { mTmpOccupied.copyTo(mOccupied);
for (int j = 0; j < mCountY; j++) {
mOccupied[i][j] = mTmpOccupied[i][j];
}
}
long screenId = mLauncher.getWorkspace().getIdForScreen(this); long screenId = mLauncher.getWorkspace().getIdForScreen(this);
int container = Favorites.CONTAINER_DESKTOP; int container = Favorites.CONTAINER_DESKTOP;
@@ -2241,10 +2201,10 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
resultSpan); resultSpan);
if (result[0] >= 0 && result[1] >= 0) { if (result[0] >= 0 && result[1] >= 0) {
copyCurrentStateToSolution(solution, false); copyCurrentStateToSolution(solution, false);
solution.dragViewX = result[0]; solution.cellX = result[0];
solution.dragViewY = result[1]; solution.cellY = result[1];
solution.dragViewSpanX = resultSpan[0]; solution.spanX = resultSpan[0];
solution.dragViewSpanY = resultSpan[1]; solution.spanY = resultSpan[1];
solution.isSolution = true; solution.isSolution = true;
} else { } else {
solution.isSolution = false; solution.isSolution = false;
@@ -2432,10 +2392,10 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
if (finalSolution != null) { if (finalSolution != null) {
beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0, beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
ReorderPreviewAnimation.MODE_HINT); ReorderPreviewAnimation.MODE_HINT);
result[0] = finalSolution.dragViewX; result[0] = finalSolution.cellX;
result[1] = finalSolution.dragViewY; result[1] = finalSolution.cellY;
resultSpan[0] = finalSolution.dragViewSpanX; resultSpan[0] = finalSolution.spanX;
resultSpan[1] = finalSolution.dragViewSpanY; resultSpan[1] = finalSolution.spanY;
} else { } else {
result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1; result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
} }
@@ -2448,10 +2408,10 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
} }
if (finalSolution != null) { if (finalSolution != null) {
result[0] = finalSolution.dragViewX; result[0] = finalSolution.cellX;
result[1] = finalSolution.dragViewY; result[1] = finalSolution.cellY;
resultSpan[0] = finalSolution.dragViewSpanX; resultSpan[0] = finalSolution.spanX;
resultSpan[1] = finalSolution.dragViewSpanY; resultSpan[1] = finalSolution.spanY;
// If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
// committing anything or animating anything as we just want to determine if a solution // committing anything or animating anything as we just want to determine if a solution
@@ -2493,25 +2453,24 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
return mItemPlacementDirty; return mItemPlacementDirty;
} }
@Thunk class ItemConfiguration { private static class ItemConfiguration extends CellAndSpan {
HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>(); HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>(); private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
ArrayList<View> sortedViews = new ArrayList<View>(); ArrayList<View> sortedViews = new ArrayList<View>();
ArrayList<View> intersectingViews; ArrayList<View> intersectingViews;
boolean isSolution = false; boolean isSolution = false;
int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
void save() { void save() {
// Copy current state into savedMap // Copy current state into savedMap
for (View v: map.keySet()) { for (View v: map.keySet()) {
map.get(v).copy(savedMap.get(v)); savedMap.get(v).copyFrom(map.get(v));
} }
} }
void restore() { void restore() {
// Restore current state from savedMap // Restore current state from savedMap
for (View v: savedMap.keySet()) { for (View v: savedMap.keySet()) {
savedMap.get(v).copy(map.get(v)); map.get(v).copyFrom(savedMap.get(v));
} }
} }
@@ -2522,35 +2481,21 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
} }
int area() { int area() {
return dragViewSpanX * dragViewSpanY; return spanX * spanY;
}
}
private class CellAndSpan {
int x, y;
int spanX, spanY;
public CellAndSpan() {
} }
public void copy(CellAndSpan copy) { void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
copy.x = x; boolean first = true;
copy.y = y; for (View v: views) {
copy.spanX = spanX; CellAndSpan c = map.get(v);
copy.spanY = spanY; if (first) {
outRect.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
first = false;
} else {
outRect.union(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
}
}
} }
public CellAndSpan(int x, int y, int spanX, int spanY) {
this.x = x;
this.y = y;
this.spanX = spanX;
this.spanY = spanY;
}
public String toString() {
return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
}
} }
/** /**
@@ -2588,33 +2533,10 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
* @return True if a vacant cell of the specified dimension was found, false otherwise. * @return True if a vacant cell of the specified dimension was found, false otherwise.
*/ */
public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) { public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
boolean foundCell = false; if (cellXY == null) {
final int endX = mCountX - (spanX - 1); cellXY = new int[2];
final int endY = mCountY - (spanY - 1);
for (int y = 0; y < endY && !foundCell; y++) {
inner:
for (int x = 0; x < endX; x++) {
for (int i = 0; i < spanX; i++) {
for (int j = 0; j < spanY; j++) {
if (mOccupied[x + i][y + j]) {
// small optimization: we can skip to after the column we just found
// an occupied cell
x += i;
continue inner;
}
}
}
if (cellXY != null) {
cellXY[0] = x;
cellXY[1] = y;
}
foundCell = true;
break;
}
} }
return mOccupied.findVacantCell(cellXY, spanX, spanY);
return foundCell;
} }
/** /**
@@ -2688,34 +2610,16 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
resultRect.set(x, y, x + width, y + height); resultRect.set(x, y, x + width, y + height);
} }
private void clearOccupiedCells() {
for (int x = 0; x < mCountX; x++) {
for (int y = 0; y < mCountY; y++) {
mOccupied[x][y] = false;
}
}
}
public void markCellsAsOccupiedForView(View view) { public void markCellsAsOccupiedForView(View view) {
if (view == null || view.getParent() != mShortcutsAndWidgets) return; if (view == null || view.getParent() != mShortcutsAndWidgets) return;
LayoutParams lp = (LayoutParams) view.getLayoutParams(); LayoutParams lp = (LayoutParams) view.getLayoutParams();
markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true); mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
} }
public void markCellsAsUnoccupiedForView(View view) { public void markCellsAsUnoccupiedForView(View view) {
if (view == null || view.getParent() != mShortcutsAndWidgets) return; if (view == null || view.getParent() != mShortcutsAndWidgets) return;
LayoutParams lp = (LayoutParams) view.getLayoutParams(); LayoutParams lp = (LayoutParams) view.getLayoutParams();
markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false); mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
}
private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
boolean value) {
if (cellX < 0 || cellY < 0) return;
for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
occupied[x][y] = value;
}
}
} }
public int getDesiredWidth() { public int getDesiredWidth() {
@@ -2730,7 +2634,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
public boolean isOccupied(int x, int y) { public boolean isOccupied(int x, int y) {
if (x < mCountX && y < mCountY) { if (x < mCountX && y < mCountY) {
return mOccupied[x][y]; return mOccupied.cells[x][y];
} else { } else {
throw new RuntimeException("Position exceeds the bound of this CellLayout"); throw new RuntimeException("Position exceeds the bound of this CellLayout");
} }
@@ -2909,21 +2813,17 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
// 2. When long clicking on an empty cell in a CellLayout, we save information about the // 2. When long clicking on an empty cell in a CellLayout, we save information about the
// cellX and cellY coordinates and which page was clicked. We then set this as a tag on // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
// the CellLayout that was long clicked // the CellLayout that was long clicked
public static final class CellInfo { public static final class CellInfo extends CellAndSpan {
public View cell; public View cell;
int cellX = -1;
int cellY = -1;
int spanX;
int spanY;
long screenId; long screenId;
long container; long container;
public CellInfo(View v, ItemInfo info) { public CellInfo(View v, ItemInfo info) {
cell = v;
cellX = info.cellX; cellX = info.cellX;
cellY = info.cellY; cellY = info.cellY;
spanX = info.spanX; spanX = info.spanX;
spanY = info.spanY; spanY = info.spanY;
cell = v;
screenId = info.screenId; screenId = info.screenId;
container = info.container; container = info.container;
} }
@@ -2935,10 +2835,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
} }
} }
public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
}
/** /**
* Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
* if necessary). * if necessary).
@@ -2960,19 +2856,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
} }
public boolean isRegionVacant(int x, int y, int spanX, int spanY) { public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
int x2 = x + spanX - 1; return mOccupied.isRegionVacant(x, y, spanX, spanY);
int y2 = y + spanY - 1;
if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
return false;
}
for (int i = x; i <= x2; i++) {
for (int j = y; j <= y2; j++) {
if (mOccupied[i][j]) {
return false;
}
}
}
return true;
} }
} }
+31 -57
View File
@@ -61,6 +61,7 @@ import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.CursorIconInfo; import com.android.launcher3.util.CursorIconInfo;
import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.ManagedProfileHeuristic;
import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageManagerHelper;
@@ -374,21 +375,14 @@ public class LauncherModel extends BroadcastReceiver
int[] xy, int spanX, int spanY) { int[] xy, int spanX, int spanY) {
LauncherAppState app = LauncherAppState.getInstance(); LauncherAppState app = LauncherAppState.getInstance();
InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
final int xCount = (int) profile.numColumns;
final int yCount = (int) profile.numRows; GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
boolean[][] occupied = new boolean[xCount][yCount];
if (occupiedPos != null) { if (occupiedPos != null) {
for (ItemInfo r : occupiedPos) { for (ItemInfo r : occupiedPos) {
int right = r.cellX + r.spanX; occupied.markCells(r, true);
int bottom = r.cellY + r.spanY;
for (int x = r.cellX; 0 <= x && x < right && x < xCount; x++) {
for (int y = r.cellY; 0 <= y && y < bottom && y < yCount; y++) {
occupied[x][y] = true;
}
}
} }
} }
return Utilities.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied); return occupied.findVacantCell(xy, spanX, spanY);
} }
/** /**
@@ -1468,12 +1462,10 @@ public class LauncherModel extends BroadcastReceiver
} }
// check & update map of what's occupied; used to discard overlapping/invalid items // check & update map of what's occupied; used to discard overlapping/invalid items
private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item, private boolean checkItemPlacement(LongArrayMap<GridOccupancy> occupied, ItemInfo item,
ArrayList<Long> workspaceScreens) { ArrayList<Long> workspaceScreens) {
LauncherAppState app = LauncherAppState.getInstance(); LauncherAppState app = LauncherAppState.getInstance();
InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
final int countX = profile.numColumns;
final int countY = profile.numRows;
long containerIndex = item.screenId; long containerIndex = item.screenId;
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
@@ -1486,7 +1478,7 @@ public class LauncherModel extends BroadcastReceiver
return false; return false;
} }
final ItemInfo[][] hotseatItems = final GridOccupancy hotseatOccupancy =
occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT); occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
if (item.screenId >= profile.numHotseatIcons) { if (item.screenId >= profile.numHotseatIcons) {
@@ -1497,22 +1489,20 @@ public class LauncherModel extends BroadcastReceiver
return false; return false;
} }
if (hotseatItems != null) { if (hotseatOccupancy != null) {
if (hotseatItems[(int) item.screenId][0] != null) { if (hotseatOccupancy.cells[(int) item.screenId][0]) {
Log.e(TAG, "Error loading shortcut into hotseat " + item Log.e(TAG, "Error loading shortcut into hotseat " + item
+ " into position (" + item.screenId + ":" + item.cellX + "," + " into position (" + item.screenId + ":" + item.cellX + ","
+ item.cellY + ") occupied by " + item.cellY + ") already occupied");
+ occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
[(int) item.screenId][0]);
return false; return false;
} else { } else {
hotseatItems[(int) item.screenId][0] = item; hotseatOccupancy.cells[(int) item.screenId][0] = true;
return true; return true;
} }
} else { } else {
final ItemInfo[][] items = new ItemInfo[(int) profile.numHotseatIcons][1]; final GridOccupancy occupancy = new GridOccupancy(profile.numHotseatIcons, 1);
items[(int) item.screenId][0] = item; occupancy.cells[(int) item.screenId][0] = true;
occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items); occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy);
return true; return true;
} }
} else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
@@ -1525,12 +1515,8 @@ public class LauncherModel extends BroadcastReceiver
return true; return true;
} }
if (!occupied.containsKey(item.screenId)) { final int countX = profile.numColumns;
ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1]; final int countY = profile.numRows;
occupied.put(item.screenId, items);
}
final ItemInfo[][] screens = occupied.get(item.screenId);
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
item.cellX < 0 || item.cellY < 0 || item.cellX < 0 || item.cellY < 0 ||
item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) { item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
@@ -1541,26 +1527,22 @@ public class LauncherModel extends BroadcastReceiver
return false; return false;
} }
// Check if any workspace icons overlap with each other if (!occupied.containsKey(item.screenId)) {
for (int x = item.cellX; x < (item.cellX+item.spanX); x++) { occupied.put(item.screenId, new GridOccupancy(countX + 1, countY + 1));
for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
if (screens[x][y] != null) {
Log.e(TAG, "Error loading shortcut " + item
+ " into cell (" + containerIndex + "-" + item.screenId + ":"
+ x + "," + y
+ ") occupied by "
+ screens[x][y]);
return false;
}
}
}
for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
screens[x][y] = item;
}
} }
final GridOccupancy occupancy = occupied.get(item.screenId);
return true; // Check if any workspace icons overlap with each other
if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) {
occupancy.markCells(item, true);
return true;
} else {
Log.e(TAG, "Error loading shortcut " + item
+ " into cell (" + containerIndex + "-" + item.screenId + ":"
+ item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY
+ ") already occupied");
return false;
}
} }
/** Clears all the sBg data structures */ /** Clears all the sBg data structures */
@@ -1621,7 +1603,7 @@ public class LauncherModel extends BroadcastReceiver
// +1 for the hotseat (it can be larger than the workspace) // +1 for the hotseat (it can be larger than the workspace)
// Load workspace in reverse order to ensure that latest items are loaded first (and // Load workspace in reverse order to ensure that latest items are loaded first (and
// before any earlier duplicates) // before any earlier duplicates)
final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>(); final LongArrayMap<GridOccupancy> occupied = new LongArrayMap<>();
HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null; HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
try { try {
@@ -2185,14 +2167,6 @@ public class LauncherModel extends BroadcastReceiver
if (screenId > 0) { if (screenId > 0) {
line += " | "; line += " | ";
} }
ItemInfo[][] screen = occupied.valueAt(i);
for (int x = 0; x < countX; x++) {
if (x < screen.length && y < screen[x].length) {
line += (screen[x][y] != null) ? "#" : ".";
} else {
line += "!";
}
}
} }
Log.d(TAG, "[ " + line + " ]"); Log.d(TAG, "[ " + line + " ]");
} }
-33
View File
@@ -654,39 +654,6 @@ public final class Utilities {
} }
} }
/**
* Find the first vacant cell, if there is one.
*
* @param vacant Holds the x and y coordinate of the vacant cell
* @param spanX Horizontal cell span.
* @param spanY Vertical cell span.
*
* @return true if a vacant cell was found
*/
public static boolean findVacantCell(int[] vacant, int spanX, int spanY,
int xCount, int yCount, boolean[][] occupied) {
for (int y = 0; (y + spanY) <= yCount; y++) {
for (int x = 0; (x + spanX) <= xCount; x++) {
boolean available = !occupied[x][y];
out: for (int i = x; i < x + spanX; i++) {
for (int j = y; j < y + spanY; j++) {
available = available && !occupied[i][j];
if (!available) break out;
}
}
if (available) {
vacant[0] = x;
vacant[1] = y;
return true;
}
}
}
return false;
}
/** /**
* Trims the string, removing all whitespace at the beginning and end of the string. * Trims the string, removing all whitespace at the beginning and end of the string.
* Non-breaking whitespaces are also removed. * Non-breaking whitespaces are also removed.
@@ -25,6 +25,7 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.backup.nano.BackupProtos; import com.android.launcher3.backup.nano.BackupProtos;
import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.LongArrayMap;
import java.util.ArrayList; import java.util.ArrayList;
@@ -220,7 +221,7 @@ public class GridSizeMigrationTask {
// {@link #mCarryOver}, to prevent an infinite loop. If no item could be removed, // {@link #mCarryOver}, to prevent an infinite loop. If no item could be removed,
// break the loop and abort migration by throwing an exception. // break the loop and abort migration by throwing an exception.
OptimalPlacementSolution placement = new OptimalPlacementSolution( OptimalPlacementSolution placement = new OptimalPlacementSolution(
new boolean[mTrgX][mTrgY], deepCopy(mCarryOver), true); new GridOccupancy(mTrgX, mTrgY), deepCopy(mCarryOver), true);
placement.find(); placement.find();
if (placement.finalPlacedItems.size() > 0) { if (placement.finalPlacedItems.size() > 0) {
long newScreenId = LauncherSettings.Settings.call( long newScreenId = LauncherSettings.Settings.call(
@@ -336,9 +337,9 @@ public class GridSizeMigrationTask {
if (!mCarryOver.isEmpty() && removeWt == 0) { if (!mCarryOver.isEmpty() && removeWt == 0) {
// No new items were removed in this step. Try placing all the items on this screen. // No new items were removed in this step. Try placing all the items on this screen.
boolean[][] occupied = new boolean[mTrgX][mTrgY]; GridOccupancy occupied = new GridOccupancy(mTrgX, mTrgY);
for (DbEntry item : finalItems) { for (DbEntry item : finalItems) {
markCells(occupied, item, true); occupied.markCells(item, true);
} }
OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied, OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied,
@@ -376,7 +377,7 @@ public class GridSizeMigrationTask {
*/ */
private ArrayList<DbEntry> tryRemove(int col, int row, ArrayList<DbEntry> items, private ArrayList<DbEntry> tryRemove(int col, int row, ArrayList<DbEntry> items,
float[] outLoss) { float[] outLoss) {
boolean[][] occupied = new boolean[mTrgX][mTrgY]; GridOccupancy occupied = new GridOccupancy(mTrgX, mTrgY);
col = mShouldRemoveX ? col : Integer.MAX_VALUE; col = mShouldRemoveX ? col : Integer.MAX_VALUE;
row = mShouldRemoveY ? row : Integer.MAX_VALUE; row = mShouldRemoveY ? row : Integer.MAX_VALUE;
@@ -394,7 +395,7 @@ public class GridSizeMigrationTask {
if (item.cellX > col) item.cellX --; if (item.cellX > col) item.cellX --;
if (item.cellY > row) item.cellY --; if (item.cellY > row) item.cellY --;
finalItems.add(item); finalItems.add(item);
markCells(occupied, item, true); occupied.markCells(item, true);
} }
} }
@@ -406,31 +407,9 @@ public class GridSizeMigrationTask {
return finalItems; return finalItems;
} }
private void markCells(boolean[][] occupied, DbEntry item, boolean val) {
for (int i = item.cellX; i < (item.cellX + item.spanX); i++) {
for (int j = item.cellY; j < (item.cellY + item.spanY); j++) {
occupied[i][j] = val;
}
}
}
private boolean isVacant(boolean[][] occupied, int x, int y, int w, int h) {
if (x + w > mTrgX) return false;
if (y + h > mTrgY) return false;
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
if (occupied[i + x][j + y]) {
return false;
}
}
}
return true;
}
private class OptimalPlacementSolution { private class OptimalPlacementSolution {
private final ArrayList<DbEntry> itemsToPlace; private final ArrayList<DbEntry> itemsToPlace;
private final boolean[][] occupied; private final GridOccupancy occupied;
// If set to true, item movement are not considered in move cost, leading to a more // If set to true, item movement are not considered in move cost, leading to a more
// linear placement. // linear placement.
@@ -440,11 +419,11 @@ public class GridSizeMigrationTask {
float lowestMoveCost = Float.MAX_VALUE; float lowestMoveCost = Float.MAX_VALUE;
ArrayList<DbEntry> finalPlacedItems; ArrayList<DbEntry> finalPlacedItems;
public OptimalPlacementSolution(boolean[][] occupied, ArrayList<DbEntry> itemsToPlace) { public OptimalPlacementSolution(GridOccupancy occupied, ArrayList<DbEntry> itemsToPlace) {
this(occupied, itemsToPlace, false); this(occupied, itemsToPlace, false);
} }
public OptimalPlacementSolution(boolean[][] occupied, ArrayList<DbEntry> itemsToPlace, public OptimalPlacementSolution(GridOccupancy occupied, ArrayList<DbEntry> itemsToPlace,
boolean ignoreMove) { boolean ignoreMove) {
this.occupied = occupied; this.occupied = occupied;
this.itemsToPlace = itemsToPlace; this.itemsToPlace = itemsToPlace;
@@ -513,42 +492,42 @@ public class GridSizeMigrationTask {
newMoveCost = moveCost; newMoveCost = moveCost;
} }
if (isVacant(occupied, x, y, myW, myH)) { if (occupied.isRegionVacant(x, y, myW, myH)) {
// place at this position and continue search. // place at this position and continue search.
markCells(occupied, me, true); occupied.markCells(me, true);
find(index + 1, weightLoss, newMoveCost, itemsIncludingMe); find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
markCells(occupied, me, false); occupied.markCells(me, false);
} }
// Try resizing horizontally // Try resizing horizontally
if (myW > me.minSpanX && isVacant(occupied, x, y, myW - 1, myH)) { if (myW > me.minSpanX && occupied.isRegionVacant(x, y, myW - 1, myH)) {
me.spanX --; me.spanX --;
markCells(occupied, me, true); occupied.markCells(me, true);
// 1 extra move cost // 1 extra move cost
find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe); find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
markCells(occupied, me, false); occupied.markCells(me, false);
me.spanX ++; me.spanX ++;
} }
// Try resizing vertically // Try resizing vertically
if (myH > me.minSpanY && isVacant(occupied, x, y, myW, myH - 1)) { if (myH > me.minSpanY && occupied.isRegionVacant(x, y, myW, myH - 1)) {
me.spanY --; me.spanY --;
markCells(occupied, me, true); occupied.markCells(me, true);
// 1 extra move cost // 1 extra move cost
find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe); find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
markCells(occupied, me, false); occupied.markCells(me, false);
me.spanY ++; me.spanY ++;
} }
// Try resizing horizontally & vertically // Try resizing horizontally & vertically
if (myH > me.minSpanY && myW > me.minSpanX && if (myH > me.minSpanY && myW > me.minSpanX &&
isVacant(occupied, x, y, myW - 1, myH - 1)) { occupied.isRegionVacant(x, y, myW - 1, myH - 1)) {
me.spanX --; me.spanX --;
me.spanY --; me.spanY --;
markCells(occupied, me, true); occupied.markCells(me, true);
// 2 extra move cost // 2 extra move cost
find(index + 1, weightLoss, newMoveCost + 2, itemsIncludingMe); find(index + 1, weightLoss, newMoveCost + 2, itemsIncludingMe);
markCells(occupied, me, false); occupied.markCells(me, false);
me.spanX ++; me.spanX ++;
me.spanY ++; me.spanY ++;
} }
@@ -570,7 +549,7 @@ public class GridSizeMigrationTask {
for (int y = 0; y < mTrgY; y++) { for (int y = 0; y < mTrgY; y++) {
for (int x = 0; x < mTrgX; x++) { for (int x = 0; x < mTrgX; x++) {
if (!occupied[x][y]) { if (!occupied.cells[x][y]) {
int dist = ignoreMove ? 0 : int dist = ignoreMove ? 0 :
((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY - y)); ((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY - y));
if (dist < newDistance) { if (dist < newDistance) {
@@ -595,9 +574,9 @@ public class GridSizeMigrationTask {
if (ignoreMove) { if (ignoreMove) {
newMoveCost = moveCost; newMoveCost = moveCost;
} }
markCells(occupied, me, true); occupied.markCells(me, true);
find(index + 1, weightLoss, newMoveCost, itemsIncludingMe); find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
markCells(occupied, me, false); occupied.markCells(me, false);
me.cellX = myX; me.cellX = myX;
me.cellY = myY; me.cellY = myY;
@@ -0,0 +1,48 @@
package com.android.launcher3.util;
/**
* Base class which represents an area on the grid.
*/
public class CellAndSpan {
/**
* Indicates the X position of the associated cell.
*/
public int cellX = -1;
/**
* Indicates the Y position of the associated cell.
*/
public int cellY = -1;
/**
* Indicates the X cell span.
*/
public int spanX = 1;
/**
* Indicates the Y cell span.
*/
public int spanY = 1;
public CellAndSpan() {
}
public void copyFrom(CellAndSpan copy) {
cellX = copy.cellX;
cellY = copy.cellY;
spanX = copy.spanX;
spanY = copy.spanY;
}
public CellAndSpan(int cellX, int cellY, int spanX, int spanY) {
this.cellX = cellX;
this.cellY = cellY;
this.spanX = spanX;
this.spanY = spanY;
}
public String toString() {
return "(" + cellX + ", " + cellY + ": " + spanX + ", " + spanY + ")";
}
}
@@ -0,0 +1,101 @@
package com.android.launcher3.util;
import android.graphics.Rect;
import com.android.launcher3.ItemInfo;
/**
* Utility object to manage the occupancy in a grid.
*/
public class GridOccupancy {
private final int mCountX;
private final int mCountY;
public final boolean[][] cells;
public GridOccupancy(int countX, int countY) {
mCountX = countX;
mCountY = countY;
cells = new boolean[countX][countY];
}
/**
* Find the first vacant cell, if there is one.
*
* @param vacantOut Holds the x and y coordinate of the vacant cell
* @param spanX Horizontal cell span.
* @param spanY Vertical cell span.
*
* @return true if a vacant cell was found
*/
public boolean findVacantCell(int[] vacantOut, int spanX, int spanY) {
for (int y = 0; (y + spanY) <= mCountY; y++) {
for (int x = 0; (x + spanX) <= mCountX; x++) {
boolean available = !cells[x][y];
out:
for (int i = x; i < x + spanX; i++) {
for (int j = y; j < y + spanY; j++) {
available = available && !cells[i][j];
if (!available) break out;
}
}
if (available) {
vacantOut[0] = x;
vacantOut[1] = y;
return true;
}
}
}
return false;
}
public void copyTo(GridOccupancy dest) {
for (int i = 0; i < mCountX; i++) {
for (int j = 0; j < mCountY; j++) {
dest.cells[i][j] = cells[i][j];
}
}
}
public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
int x2 = x + spanX - 1;
int y2 = y + spanY - 1;
if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
return false;
}
for (int i = x; i <= x2; i++) {
for (int j = y; j <= y2; j++) {
if (cells[i][j]) {
return false;
}
}
}
return true;
}
public void markCells(int cellX, int cellY, int spanX, int spanY, boolean value) {
if (cellX < 0 || cellY < 0) return;
for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
for (int y = cellY; y < cellY + spanY && y < mCountX; y++) {
cells[x][y] = value;
}
}
}
public void markCells(Rect r, boolean value) {
markCells(r.left, r.top, r.width(), r.height(), value);
}
public void markCells(CellAndSpan cell, boolean value) {
markCells(cell.cellX, cell.cellY, cell.spanX, cell.spanY, value);
}
public void markCells(ItemInfo item, boolean value) {
markCells(item.cellX, item.cellY, item.spanX, item.spanY, value);
}
public void clear() {
markCells(0, 0, mCountX, mCountY, false);
}
}
@@ -14,7 +14,7 @@
* the License. * the License.
*/ */
package com.android.launcher3; package com.android.launcher3.util;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.SmallTest;
@@ -0,0 +1,61 @@
package com.android.launcher3.util;
import android.test.suitebuilder.annotation.SmallTest;
import junit.framework.TestCase;
/**
* Unit tests for {@link GridOccupancy}
*/
@SmallTest
public class GridOccupancyTest extends TestCase {
public void testFindVacantCell() {
GridOccupancy grid = initGrid(4,
1, 1, 1, 0, 0,
0, 0, 1, 1, 0,
0, 0, 0, 0, 0,
1, 1, 0, 0, 0
);
int[] vacant = new int[2];
assertTrue(grid.findVacantCell(vacant, 2, 2));
assertEquals(vacant[0], 0);
assertEquals(vacant[1], 1);
assertTrue(grid.findVacantCell(vacant, 3, 2));
assertEquals(vacant[0], 2);
assertEquals(vacant[1], 2);
assertFalse(grid.findVacantCell(vacant, 3, 3));
}
public void testIsRegionVacant() {
GridOccupancy grid = initGrid(4,
1, 1, 1, 0, 0,
0, 0, 1, 1, 0,
0, 0, 0, 0, 0,
1, 1, 0, 0, 0
);
assertTrue(grid.isRegionVacant(4, 0, 1, 4));
assertTrue(grid.isRegionVacant(0, 1, 2, 2));
assertTrue(grid.isRegionVacant(2, 2, 3, 2));
assertFalse(grid.isRegionVacant(3, 0, 2, 4));
assertFalse(grid.isRegionVacant(0, 0, 2, 1));
}
private GridOccupancy initGrid(int rows, int... cells) {
int cols = cells.length / rows;
int i = 0;
GridOccupancy grid = new GridOccupancy(cols, rows);
for (int y = 0; y < rows; y++) {
for (int x = 0; x < cols; x++) {
grid.cells[x][y] = cells[i] != 0;
i++;
}
}
return grid;
}
}