diff options
author | Steve Kondik <shade@chemlab.org> | 2013-02-15 14:45:54 -0800 |
---|---|---|
committer | Steve Kondik <shade@chemlab.org> | 2013-02-15 14:45:54 -0800 |
commit | ed1816e3a828046439da5cb31a65af4641be9076 (patch) | |
tree | 994e4ed51b3e1630e338b3ec255a8f4dce429953 /src/com/cyanogenmod | |
parent | c43d6eebe95dcabcb4ded1c4da8c6cc21d2514f5 (diff) | |
parent | 002c25eede2a33a5378c16c5bb9165179783e729 (diff) | |
download | android_packages_apps_Trebuchet-ed1816e3a828046439da5cb31a65af4641be9076.tar.gz android_packages_apps_Trebuchet-ed1816e3a828046439da5cb31a65af4641be9076.tar.bz2 android_packages_apps_Trebuchet-ed1816e3a828046439da5cb31a65af4641be9076.zip |
Merge tag 'android-4.2.2_r1' of https://android.googlesource.com/platform/packages/apps/Launcher2 into treb
Android 4.2.2 release 1
Conflicts:
res/layout-land/launcher.xml
res/layout-port/launcher.xml
res/layout-sw720dp/launcher.xml
src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java
src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java
src/com/cyanogenmod/trebuchet/CellLayout.java
src/com/cyanogenmod/trebuchet/Launcher.java
src/com/cyanogenmod/trebuchet/PagedView.java
Change-Id: I415138430337bcd21070b815a80de9c0cdd52239
Diffstat (limited to 'src/com/cyanogenmod')
-rw-r--r-- | src/com/cyanogenmod/trebuchet/AddAdapter.java | 2 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java | 6 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java | 3 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java | 159 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/CellLayout.java | 521 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/Launcher.java | 66 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/LauncherAppWidgetInfo.java | 1 | ||||
-rw-r--r-- | src/com/cyanogenmod/trebuchet/PagedView.java | 16 |
8 files changed, 511 insertions, 263 deletions
diff --git a/src/com/cyanogenmod/trebuchet/AddAdapter.java b/src/com/cyanogenmod/trebuchet/AddAdapter.java index 5a3d43d05..8f4e8fcfd 100644 --- a/src/com/cyanogenmod/trebuchet/AddAdapter.java +++ b/src/com/cyanogenmod/trebuchet/AddAdapter.java @@ -71,7 +71,7 @@ public class AddAdapter extends BaseAdapter { Resources res = launcher.getResources(); mItems.add(new ListItem(res, R.string.group_wallpapers, - R.drawable.ic_launcher_wallpaper, ITEM_WALLPAPER)); + R.mipmap.ic_launcher_wallpaper, ITEM_WALLPAPER)); } public View getView(int position, View convertView, ViewGroup parent) { diff --git a/src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java b/src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java index 5b868c179..48d41160d 100644 --- a/src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java +++ b/src/com/cyanogenmod/trebuchet/AppWidgetResizeFrame.java @@ -389,8 +389,10 @@ public class AppWidgetResizeFrame extends FrameLayout { public void snapToWidget(boolean animate) { final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); - int xOffset = mCellLayout.getLeft() + mCellLayout.getPaddingLeft() - mWorkspace.getScrollX(); - int yOffset = mCellLayout.getTop() + mCellLayout.getPaddingTop() - mWorkspace.getScrollY(); + int xOffset = mCellLayout.getLeft() + mCellLayout.getPaddingLeft() + + mDragLayer.getPaddingLeft() - mWorkspace.getScrollX(); + int yOffset = mCellLayout.getTop() + mCellLayout.getPaddingTop() + + mDragLayer.getPaddingTop() - mWorkspace.getScrollY(); int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding - mWidgetPaddingLeft - mWidgetPaddingRight; diff --git a/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java b/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java index 8d4e4d791..9f7595d96 100644 --- a/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java +++ b/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java @@ -600,9 +600,10 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen int[] pos = mWidgetSpacingLayout.estimateCellPosition(mClingFocusedX, mClingFocusedY); mLauncher.getDragLayer().getLocationInDragLayer(this, offset); // PagedViews are centered horizontally but top aligned + // Note we have to shift the items up now that Launcher sits under the status bar pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 + offset[0]; - pos[1] += offset[1]; + pos[1] += offset[1] - mLauncher.getDragLayer().getPaddingTop(); mLauncher.showFirstRunAllAppsCling(pos); } else if (!mHasShownAllAppsSortCling && isDataReady() && allAppsCling != null && allAppsCling.isDismissed()) { diff --git a/src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java b/src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java index 08d959753..067b98ff2 100644 --- a/src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java +++ b/src/com/cyanogenmod/trebuchet/AppsCustomizeTabHost.java @@ -232,81 +232,87 @@ public class AppsCustomizeTabHost extends TabHost implements LauncherTransitiona post(new Runnable() { @Override public void run() { - if (mAppsCustomizePane.getMeasuredWidth() <= 0 || - mAppsCustomizePane.getMeasuredHeight() <= 0) { - reloadCurrentPage(); - return; - } - // Take the visible pages and re-parent them temporarily to mAnimatorBuffer - // and then cross fade to the new pages - int[] visiblePageRange = new int[2]; - mAppsCustomizePane.getVisiblePages(visiblePageRange); - if (visiblePageRange[0] == -1 && visiblePageRange[1] == -1) { - // If we can't get the visible page ranges, then just skip the animation - reloadCurrentPage(); - return; - } - ArrayList<View> visiblePages = new ArrayList<View>(); - for (int i = visiblePageRange[0]; i <= visiblePageRange[1]; i++) { - visiblePages.add(mAppsCustomizePane.getPageAt(i)); - } - // We want the pages to be rendered in exactly the same way as they were when - // their parent was mAppsCustomizePane -- so set the scroll on mAnimationBuffer - // to be exactly the same as mAppsCustomizePane, and below, set the left/top - // parameters to be correct for each of the pages - mAnimationBuffer.scrollTo(mAppsCustomizePane.getScrollX(), 0); - - // mAppsCustomizePane renders its children in reverse order, so - // add the pages to mAnimationBuffer in reverse order to match that behavior - for (int i = visiblePages.size() - 1; i >= 0; i--) { - View child = visiblePages.get(i); - if (child instanceof PagedViewCellLayout) { - ((PagedViewCellLayout) child).resetChildrenOnKeyListeners(); - } else if (child instanceof PagedViewGridLayout) { - ((PagedViewGridLayout) child).resetChildrenOnKeyListeners(); + if (mAppsCustomizePane.getMeasuredWidth() <= 0 || + mAppsCustomizePane.getMeasuredHeight() <= 0) { + reloadCurrentPage(); + return; } - PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(false); - mAppsCustomizePane.removeView(child); - PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(true); - mAnimationBuffer.setAlpha(1f); - mAnimationBuffer.setVisibility(View.VISIBLE); - LayoutParams p = new FrameLayout.LayoutParams(child.getMeasuredWidth(), - child.getMeasuredHeight()); - p.setMargins(child.getLeft(), child.getTop(), 0, 0); - mAnimationBuffer.addView(child, p); - - // Toggle the new content - onTabChangedStart(); - onTabChangedEnd(type); - - // Animate the transition - ObjectAnimator outAnim = LauncherAnimUtils.ofFloat(mAnimationBuffer, "alpha", 0f); - outAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mAnimationBuffer.setVisibility(View.GONE); - mAnimationBuffer.removeAllViews(); + // Take the visible pages and re-parent them temporarily to mAnimatorBuffer + // and then cross fade to the new pages + int[] visiblePageRange = new int[2]; + mAppsCustomizePane.getVisiblePages(visiblePageRange); + if (visiblePageRange[0] == -1 && visiblePageRange[1] == -1) { + // If we can't get the visible page ranges, then just skip the animation + reloadCurrentPage(); + return; } - @Override - public void onAnimationCancel(Animator animation) { - mAnimationBuffer.setVisibility(View.GONE); - mAnimationBuffer.removeAllViews(); + ArrayList<View> visiblePages = new ArrayList<View>(); + for (int i = visiblePageRange[0]; i <= visiblePageRange[1]; i++) { + visiblePages.add(mAppsCustomizePane.getPageAt(i)); } - }); - ObjectAnimator inAnim = LauncherAnimUtils.ofFloat(mAppsCustomizePane, "alpha", 1f); - inAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - reloadCurrentPage(); + // We want the pages to be rendered in exactly the same way as they were when + // their parent was mAppsCustomizePane -- so set the scroll on mAnimationBuffer + // to be exactly the same as mAppsCustomizePane, and below, set the left/top + // parameters to be correct for each of the pages + mAnimationBuffer.scrollTo(mAppsCustomizePane.getScrollX(), 0); + + // mAppsCustomizePane renders its children in reverse order, so + // add the pages to mAnimationBuffer in reverse order to match that behavior + for (int i = visiblePages.size() - 1; i >= 0; i--) { + View child = visiblePages.get(i); + if (child instanceof PagedViewCellLayout) { + ((PagedViewCellLayout) child).resetChildrenOnKeyListeners(); + } else if (child instanceof PagedViewGridLayout) { + ((PagedViewGridLayout) child).resetChildrenOnKeyListeners(); + } + PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(false); + mAppsCustomizePane.removeView(child); + PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(true); + mAnimationBuffer.setAlpha(1f); + mAnimationBuffer.setVisibility(View.VISIBLE); + LayoutParams p = new FrameLayout.LayoutParams(child.getMeasuredWidth(), + child.getMeasuredHeight()); + p.setMargins(child.getLeft(), child.getTop(), 0, 0); + mAnimationBuffer.addView(child, p); } - }); - AnimatorSet animSet = LauncherAnimUtils.createAnimatorSet(); - animSet.playTogether(outAnim, inAnim); - animSet.setDuration(duration); - animSet.start(); - }} - }); - } + + // Toggle the new content + onTabChangedStart(); + onTabChangedEnd(type); + + // Animate the transition + ObjectAnimator outAnim = LauncherAnimUtils.ofFloat(mAnimationBuffer, "alpha", 0f); + outAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mAnimationBuffer.setVisibility(View.GONE); + mAnimationBuffer.removeAllViews(); + } + @Override + public void onAnimationCancel(Animator animation) { + mAnimationBuffer.setVisibility(View.GONE); + mAnimationBuffer.removeAllViews(); + } + }); + ObjectAnimator inAnim = LauncherAnimUtils.ofFloat(mAppsCustomizePane, "alpha", 1f); + inAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + reloadCurrentPage(); + } + }); + + final AnimatorSet animSet = LauncherAnimUtils.createAnimatorSet(); + animSet.playTogether(outAnim, inAnim); + animSet.setDuration(duration); + post(new Runnable() { + public void run() { + animSet.start(); + } + }); + } + }); + } } public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) { @@ -432,11 +438,9 @@ public class AppsCustomizeTabHost extends TabHost implements LauncherTransitiona } if (!toWorkspace) { - // Going from Workspace -> All Apps - setVisibilityOfSiblingsWithLowerZOrder(INVISIBLE); - - // Dismiss the workspace cling and show the all apps cling (if not already shown) + // Dismiss the workspace cling l.dismissWorkspaceCling(null); + // Show the all apps cling (if not already shown) mAppsCustomizePane.showAllAppsCling(); // Make sure adjacent pages are loaded (we wait until after the transition to // prevent slowing down the animation) @@ -445,6 +449,11 @@ public class AppsCustomizeTabHost extends TabHost implements LauncherTransitiona if (!LauncherApplication.isScreenLarge() && mFadeScrollingIndicator) { mAppsCustomizePane.hideScrollingIndicator(false); } + + // Going from Workspace -> All Apps + // NOTE: We should do this at the end since we check visibility state in some of the + // cling initialization/dismiss code above. + setVisibilityOfSiblingsWithLowerZOrder(INVISIBLE); } } diff --git a/src/com/cyanogenmod/trebuchet/CellLayout.java b/src/com/cyanogenmod/trebuchet/CellLayout.java index 1fd5a5fa8..a59a96e0d 100644 --- a/src/com/cyanogenmod/trebuchet/CellLayout.java +++ b/src/com/cyanogenmod/trebuchet/CellLayout.java @@ -52,6 +52,8 @@ import com.cyanogenmod.trebuchet.FolderIcon.FolderRingAnimator; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.Stack; @@ -1561,48 +1563,6 @@ public class CellLayout extends ViewGroup { return bestXY; } - private int[] findNearestAreaInDirection(int cellX, int cellY, int spanX, int spanY, - int[] direction,boolean[][] occupied, - boolean blockOccupied[][], int[] result) { - // Keep track of best-scoring drop area - final int[] bestXY = result != null ? result : new int[2]; - bestXY[0] = -1; - bestXY[1] = -1; - float bestDistance = Float.MAX_VALUE; - - // We use this to march in a single direction - if ((direction[0] != 0 && direction[1] != 0) || - (direction[0] == 0 && direction[1] == 0)) { - return bestXY; - } - - // This will only incrememnet one of x or y based on the assertion above - int x = cellX + direction[0]; - int y = cellY + direction[1]; - while (x >= 0 && x + spanX <= mCountX && y >= 0 && y + spanY <= mCountY) { - boolean fail = false; - for (int i = 0; i < spanX; i++) { - for (int j = 0; j < spanY; j++) { - if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) { - fail = true; - } - } - } - if (!fail) { - float distance = (float) - Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY)); - if (Float.compare(distance, bestDistance) < 0) { - bestDistance = distance; - bestXY[0] = x; - bestXY[1] = y; - } - } - x += direction[0]; - y += direction[1]; - } - return bestXY; - } - private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop, int[] direction, ItemConfiguration currentState) { CellAndSpan c = currentState.map.get(v); @@ -1616,118 +1576,343 @@ public class CellLayout extends ViewGroup { c.x = mTempLocation[0]; c.y = mTempLocation[1]; success = true; - } markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); return success; } - // This method looks in the specified direction to see if there are additional views adjacent - // to the current set of views. If there are, then these views are added to the current - // set of views. This is performed iteratively, giving a cascading push behaviour. - private boolean addViewInDirection(ArrayList<View> views, Rect boundingRect, int[] direction, - boolean[][] occupied, View dragView, ItemConfiguration currentState) { - boolean found = false; + /** + * This helper class defines a cluster of views. It helps with defining complex edges + * of the cluster and determining how those edges interact with other views. The edges + * essentially define a fine-grained boundary around the cluster of views -- like a more + * precise version of a bounding box. + */ + private class ViewCluster { + final static int LEFT = 0; + final static int TOP = 1; + final static int RIGHT = 2; + final static int BOTTOM = 3; + + ArrayList<View> views; + ItemConfiguration config; + Rect boundingRect = new Rect(); + + int[] leftEdge = new int[mCountY]; + int[] rightEdge = new int[mCountY]; + int[] topEdge = new int[mCountX]; + int[] bottomEdge = new int[mCountX]; + boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty; - int childCount = mShortcutsAndWidgets.getChildCount(); - Rect r0 = new Rect(boundingRect); - Rect r1 = new Rect(); + @SuppressWarnings("unchecked") + public ViewCluster(ArrayList<View> views, ItemConfiguration config) { + this.views = (ArrayList<View>) views.clone(); + this.config = config; + resetEdges(); + } - // First, we consider the rect of the views that we are trying to translate - int deltaX = 0; - int deltaY = 0; - if (direction[1] < 0) { - r0.set(r0.left, r0.top - 1, r0.right, r0.bottom - 1); - deltaY = -1; - } else if (direction[1] > 0) { - r0.set(r0.left, r0.top + 1, r0.right, r0.bottom + 1); - deltaY = 1; - } else if (direction[0] < 0) { - r0.set(r0.left - 1, r0.top, r0.right - 1, r0.bottom); - deltaX = -1; - } else if (direction[0] > 0) { - r0.set(r0.left + 1, r0.top, r0.right + 1, r0.bottom); - deltaX = 1; + void resetEdges() { + for (int i = 0; i < mCountX; i++) { + topEdge[i] = -1; + bottomEdge[i] = -1; + } + for (int i = 0; i < mCountY; i++) { + leftEdge[i] = -1; + rightEdge[i] = -1; + } + leftEdgeDirty = true; + rightEdgeDirty = true; + bottomEdgeDirty = true; + topEdgeDirty = true; + boundingRectDirty = true; + } + + void computeEdge(int which, int[] edge) { + int count = views.size(); + for (int i = 0; i < count; i++) { + CellAndSpan cs = config.map.get(views.get(i)); + switch (which) { + case LEFT: + int left = cs.x; + for (int j = cs.y; j < cs.y + cs.spanY; j++) { + if (left < edge[j] || edge[j] < 0) { + edge[j] = left; + } + } + break; + case RIGHT: + int right = cs.x + cs.spanX; + for (int j = cs.y; j < cs.y + cs.spanY; j++) { + if (right > edge[j]) { + edge[j] = right; + } + } + break; + case TOP: + int top = cs.y; + for (int j = cs.x; j < cs.x + cs.spanX; j++) { + if (top < edge[j] || edge[j] < 0) { + edge[j] = top; + } + } + break; + case BOTTOM: + int bottom = cs.y + cs.spanY; + for (int j = cs.x; j < cs.x + cs.spanX; j++) { + if (bottom > edge[j]) { + edge[j] = bottom; + } + } + break; + } + } } - // Now we see which views, if any, are being overlapped by shifting the current group - // of views in the desired direction. - for (int i = 0; i < childCount; i++) { - // We don't need to worry about views already in our group, or the current drag view. - View child = mShortcutsAndWidgets.getChildAt(i); - if (views.contains(child) || child == dragView) continue; - CellAndSpan c = currentState.map.get(child); + boolean isViewTouchingEdge(View v, int whichEdge) { + CellAndSpan cs = config.map.get(v); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY); - if (Rect.intersects(r0, r1)) { - if (!lp.canReorder) { - return false; - } - // First we verify that the view in question is at the border of the extents - // of the block of items we are pushing - if ((direction[0] < 0 && c.x == r0.left) || - (direction[0] > 0 && c.x == r0.right - 1) || - (direction[1] < 0 && c.y == r0.top) || - (direction[1] > 0 && c.y == r0.bottom - 1)) { - boolean pushed = false; - // Since the bounding rect is a coarse description of the region (there can - // be holes at the edge of the block), we need to check to verify that a solid - // piece is intersecting. This ensures that interlocking is possible. - for (int x = c.x; x < c.x + c.spanX; x++) { - for (int y = c.y; y < c.y + c.spanY; y++) { - if (occupied[x - deltaX][y - deltaY]) { - pushed = true; - break; - } - if (pushed) break; + int[] edge = getEdge(whichEdge); + + switch (whichEdge) { + case LEFT: + for (int i = cs.y; i < cs.y + cs.spanY; i++) { + if (edge[i] == cs.x + cs.spanX) { + return true; } } - if (pushed) { - views.add(child); + break; + case RIGHT: + for (int i = cs.y; i < cs.y + cs.spanY; i++) { + if (edge[i] == cs.x) { + return true; + } + } + break; + case TOP: + for (int i = cs.x; i < cs.x + cs.spanX; i++) { + if (edge[i] == cs.y + cs.spanY) { + return true; + } + } + break; + case BOTTOM: + for (int i = cs.x; i < cs.x + cs.spanX; i++) { + if (edge[i] == cs.y) { + return true; + } + } + break; + } + return false; + } + + void shift(int whichEdge, int delta) { + for (View v: views) { + CellAndSpan c = config.map.get(v); + switch (whichEdge) { + case LEFT: + c.x -= delta; + break; + case RIGHT: + c.x += delta; + break; + case TOP: + c.y -= delta; + break; + case BOTTOM: + default: + c.y += delta; + break; + } + } + resetEdges(); + } + + public void addView(View v) { + views.add(v); + resetEdges(); + } + + public Rect getBoundingRect() { + if (boundingRectDirty) { + boolean first = true; + 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); - found = true; } } } + return boundingRect; + } + + public int[] getEdge(int which) { + switch (which) { + case LEFT: + return getLeftEdge(); + case RIGHT: + return getRightEdge(); + case TOP: + return getTopEdge(); + case BOTTOM: + default: + return getBottomEdge(); + } + } + + public int[] getLeftEdge() { + if (leftEdgeDirty) { + computeEdge(LEFT, leftEdge); + } + return leftEdge; + } + + public int[] getRightEdge() { + if (rightEdgeDirty) { + computeEdge(RIGHT, rightEdge); + } + return rightEdge; + } + + public int[] getTopEdge() { + if (topEdgeDirty) { + computeEdge(TOP, topEdge); + } + return topEdge; + } + + public int[] getBottomEdge() { + if (bottomEdgeDirty) { + computeEdge(BOTTOM, bottomEdge); + } + return bottomEdge; + } + + PositionComparator comparator = new PositionComparator(); + class PositionComparator implements Comparator<View> { + int whichEdge = 0; + public int compare(View left, View right) { + CellAndSpan l = config.map.get(left); + CellAndSpan r = config.map.get(right); + switch (whichEdge) { + case LEFT: + return (r.x + r.spanX) - (l.x + l.spanX); + case RIGHT: + return l.x - r.x; + case TOP: + return (r.y + r.spanY) - (l.y + l.spanY); + case BOTTOM: + default: + return l.y - r.y; + } + } + } + + public void sortConfigurationForEdgePush(int edge) { + comparator.whichEdge = edge; + Collections.sort(config.sortedViews, comparator); } - return found; } - private void completeSetOfViewsToMove(ArrayList<View> views, Rect boundingRect, int[] direction, - View dragView, ItemConfiguration currentState) { - Rect r0 = new Rect(boundingRect); - int minRuns; + private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop, + int[] direction, View dragView, ItemConfiguration currentState) { + + ViewCluster cluster = new ViewCluster(views, currentState); + Rect clusterRect = cluster.getBoundingRect(); + int whichEdge; + int pushDistance; + boolean fail = false; - // The first thing we do is to reduce the bounding rect to first or last row or column, - // depending on the direction. Then, we add any necessary views that are already contained - // by the bounding rect, but aren't in the list of intersecting views, and will be pushed - // by something already in the intersecting views. - if (direction[1] < 0) { - r0.set(r0.left, r0.bottom - 1, r0.right, r0.bottom); - } else if (direction[1] > 0) { - r0.set(r0.left, r0.top, r0.right, r0.top + 1); - } else if (direction[0] < 0) { - r0.set(r0.right - 1, r0.top, r0.right, r0.bottom); + // Determine the edge of the cluster that will be leading the push and how far + // the cluster must be shifted. + if (direction[0] < 0) { + whichEdge = ViewCluster.LEFT; + pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left; } else if (direction[0] > 0) { - r0.set(r0.left, r0.top, r0.left + 1, r0.bottom); + whichEdge = ViewCluster.RIGHT; + pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left; + } else if (direction[1] < 0) { + whichEdge = ViewCluster.TOP; + pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top; + } else { + whichEdge = ViewCluster.BOTTOM; + pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top; + } + + // Break early for invalid push distance. + if (pushDistance <= 0) { + return false; } - minRuns = Math.max(Math.abs(boundingRect.width() - r0.width()), - Math.abs(boundingRect.height() - r0.height())) + 1; + // Mark the occupied state as false for the group of views we want to move. + for (View v: views) { + CellAndSpan c = currentState.map.get(v); + markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); + } + + // We save the current configuration -- if we fail to find a solution we will revert + // to the initial state. The process of finding a solution modifies the configuration + // in place, hence the need for revert in the failure case. + currentState.save(); + + // The pushing algorithm is simplified by considering the views in the order in which + // they would be pushed by the cluster. For example, if the cluster is leading with its + // left edge, we consider sort the views by their right edge, from right to left. + cluster.sortConfigurationForEdgePush(whichEdge); + + while (pushDistance > 0 && !fail) { + for (View v: currentState.sortedViews) { + // For each view that isn't in the cluster, we see if the leading edge of the + // cluster is contacting the edge of that view. If so, we add that view to the + // cluster. + if (!cluster.views.contains(v) && v != dragView) { + if (cluster.isViewTouchingEdge(v, whichEdge)) { + LayoutParams lp = (LayoutParams) v.getLayoutParams(); + if (!lp.canReorder) { + // The push solution includes the all apps button, this is not viable. + fail = true; + break; + } + cluster.addView(v); + CellAndSpan c = currentState.map.get(v); + + // Adding view to cluster, mark it as not occupied. + markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); + } + } + } + pushDistance--; - // Here the first number of runs (minRuns) accounts for the the comment above, and - // further runs execute based on whether the intersecting views / bounding rect need - // to be expanded to include other views that will be pushed. - while (addViewInDirection(views, r0, direction, mTmpOccupied, - dragView, currentState) || minRuns > 0) { - minRuns--; + // The cluster has been completed, now we move the whole thing over in the appropriate + // direction. + cluster.shift(whichEdge, 1); } - boundingRect.union(r0); + + boolean foundSolution = false; + clusterRect = cluster.getBoundingRect(); + + // Due to the nature of the algorithm, the only check required to verify a valid solution + // is to ensure that completed shifted cluster lies completely within the cell layout. + if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 && + clusterRect.bottom <= mCountY) { + foundSolution = true; + } else { + currentState.restore(); + } + + // In either case, we set the occupied array as marked for the location of the views + for (View v: cluster.views) { + CellAndSpan c = currentState.map.get(v); + markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); + } + + return foundSolution; } private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop, - int[] direction, boolean push, View dragView, ItemConfiguration currentState) { + int[] direction, View dragView, ItemConfiguration currentState) { if (views.size() == 0) return true; boolean success = false; @@ -1742,15 +1927,8 @@ public class CellLayout extends ViewGroup { } } - @SuppressWarnings("unchecked") - ArrayList<View> dup = (ArrayList<View>) views.clone(); - if (push) { - completeSetOfViewsToMove(dup, boundingRect, direction, dragView, - currentState); - } - // Mark the occupied state as false for the group of views we want to move. - for (View v: dup) { + for (View v: views) { CellAndSpan c = currentState.map.get(v); markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); } @@ -1760,26 +1938,21 @@ public class CellLayout extends ViewGroup { int left = boundingRect.left; // We mark more precisely which parts of the bounding rect are truly occupied, allowing // for interlocking. - for (View v: dup) { + for (View v: views) { CellAndSpan c = currentState.map.get(v); markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true); } markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true); - if (push) { - findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(), - boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation); - } else { - findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(), - boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation); - } + findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(), + boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation); // If we successfuly found a location by pushing the block of views, we commit it if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) { int deltaX = mTempLocation[0] - boundingRect.left; int deltaY = mTempLocation[1] - boundingRect.top; - for (View v: dup) { + for (View v: views) { CellAndSpan c = currentState.map.get(v); c.x += deltaX; c.y += deltaY; @@ -1788,7 +1961,7 @@ public class CellLayout extends ViewGroup { } // In either case, we set the occupied array as marked for the location of the views - for (View v: dup) { + for (View v: views) { CellAndSpan c = currentState.map.get(v); markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); } @@ -1809,14 +1982,16 @@ public class CellLayout extends ViewGroup { // separately in each of the components. int temp = direction[1]; direction[1] = 0; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + + if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView, solution)) { return true; } direction[1] = temp; temp = direction[0]; direction[0] = 0; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + + if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView, solution)) { return true; } @@ -1828,7 +2003,7 @@ public class CellLayout extends ViewGroup { direction[1] *= -1; temp = direction[1]; direction[1] = 0; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView, solution)) { return true; } @@ -1836,7 +2011,7 @@ public class CellLayout extends ViewGroup { direction[1] = temp; temp = direction[0]; direction[0] = 0; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView, solution)) { return true; } @@ -1848,15 +2023,14 @@ public class CellLayout extends ViewGroup { } else { // If the direction vector has a single non-zero component, we push first in the // direction of the vector - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView, solution)) { return true; } - // Then we try the opposite direction direction[0] *= -1; direction[1] *= -1; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView, solution)) { return true; } @@ -1871,7 +2045,7 @@ public class CellLayout extends ViewGroup { int temp = direction[1]; direction[1] = direction[0]; direction[0] = temp; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView, solution)) { return true; } @@ -1879,7 +2053,7 @@ public class CellLayout extends ViewGroup { // Then we try the opposite direction direction[0] *= -1; direction[1] *= -1; - if (addViewsToTempLocation(intersectingViews, occupied, direction, true, + if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView, solution)) { return true; } @@ -1935,7 +2109,7 @@ public class CellLayout extends ViewGroup { } // Next we try moving the views as a block, but without requiring the push mechanic. - if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, ignoreView, + if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView, solution)) { return true; } @@ -2022,7 +2196,7 @@ public class CellLayout extends ViewGroup { } else { c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan); } - solution.map.put(child, c); + solution.add(child, c); } } @@ -2492,9 +2666,31 @@ public class CellLayout extends ViewGroup { private class ItemConfiguration { HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>(); + private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>(); + ArrayList<View> sortedViews = new ArrayList<View>(); boolean isSolution = false; int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY; + void save() { + // Copy current state into savedMap + for (View v: map.keySet()) { + map.get(v).copy(savedMap.get(v)); + } + } + + void restore() { + // Restore current state from savedMap + for (View v: savedMap.keySet()) { + savedMap.get(v).copy(map.get(v)); + } + } + + void add(View v, CellAndSpan cs) { + map.put(v, cs); + savedMap.put(v, new CellAndSpan()); + sortedViews.add(v); + } + int area() { return dragViewSpanX * dragViewSpanY; } @@ -2504,12 +2700,27 @@ public class CellLayout extends ViewGroup { int x, y; int spanX, spanY; + public CellAndSpan() { + } + + public void copy(CellAndSpan copy) { + copy.x = x; + copy.y = y; + copy.spanX = spanX; + copy.spanY = 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 + ")"; + } + } /** diff --git a/src/com/cyanogenmod/trebuchet/Launcher.java b/src/com/cyanogenmod/trebuchet/Launcher.java index bae9389d5..c69ec47be 100644 --- a/src/com/cyanogenmod/trebuchet/Launcher.java +++ b/src/com/cyanogenmod/trebuchet/Launcher.java @@ -53,9 +53,11 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; @@ -106,7 +108,6 @@ import com.cyanogenmod.trebuchet.preference.*; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileDescriptor; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; @@ -155,7 +156,10 @@ public final class Launcher extends Activity static final int DEFAULT_SCREEN = 2; private static final String PREFERENCES = "launcher.preferences"; - static final String DUMP_STATE_PROPERTY = "debug.dumpstate"; + // To turn on these properties, type + // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS] + static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate"; + static final String DUMP_STATE_PROPERTY = "launcher_dump_state"; // The Intent extra that defines whether to ignore the launch animation static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION = @@ -214,6 +218,7 @@ public final class Launcher extends Activity private Workspace mWorkspace; private View mQsbDivider; private View mDockDivider; + private View mLauncherView; private DragLayer mDragLayer; private DragController mDragController; @@ -282,6 +287,9 @@ public final class Launcher extends Activity private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2]; private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2]; + private Drawable mWorkspaceBackgroundDrawable; + private Drawable mBlackBackgroundDrawable; + private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>(); static final ArrayList<String> sDumpLogs = new ArrayList<String>(); @@ -324,6 +332,8 @@ public final class Launcher extends Activity private static ArrayList<PendingAddArguments> sPendingAddList = new ArrayList<PendingAddArguments>(); + private static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY); + private static class PendingAddArguments { int requestCode; Intent intent; @@ -333,18 +343,8 @@ public final class Launcher extends Activity int cellY; } - - private boolean doesFileExist(String filename) { - FileInputStream fis; - try { - fis = openFileInput(filename); - fis.close(); - return true; - } catch (java.io.FileNotFoundException e) { - return false; - } catch (java.io.IOException e) { - return true; - } + private static boolean isPropertyEnabled(String propertyName) { + return Log.isLoggable(propertyName, Log.VERBOSE); } @Override @@ -748,6 +748,9 @@ public final class Launcher extends Activity } mOnResumeState = State.NONE; + // Background was set to gradient in onPause(), restore to black if in all apps. + setWorkspaceBackground(mState == State.WORKSPACE); + // Process any items that were added while Launcher was away InstallShortcutReceiver.flushInstallQueue(this); @@ -933,11 +936,16 @@ public final class Launcher extends Activity private void setupViews() { final DragController dragController = mDragController; + mLauncherView = findViewById(R.id.launcher); mDragLayer = (DragLayer) findViewById(R.id.drag_layer); mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace); mQsbDivider = findViewById(R.id.qsb_divider); mDockDivider = findViewById(R.id.dock_divider); + mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg); + mBlackBackgroundDrawable = new ColorDrawable(Color.BLACK); + // Setup the drag layer mDragLayer.setup(this, dragController); @@ -967,12 +975,11 @@ public final class Launcher extends Activity } // Setup AppsCustomize - mAppsCustomizeTabHost = (AppsCustomizeTabHost) - findViewById(R.id.apps_customize_pane); + mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane); mAppsCustomizeContent = (AppsCustomizePagedView) mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content); mAppsCustomizeContent.setup(this, dragController); - + // Setup the drag controller (drop targets have to be added in reverse order in priority) dragController.setDragScoller(mWorkspace); dragController.setScrollView(mDragLayer); @@ -1437,6 +1444,10 @@ public final class Launcher extends Activity Runnable processIntent = new Runnable() { public void run() { + if (mWorkspace == null) { + // Can be cases where mWorkspace is null, this prevents a NPE + return; + } Folder openFolder = mWorkspace.getOpenFolder(); // In all these cases, only animate if we're already on home mWorkspace.exitWidgetResizeMode(); @@ -1923,7 +1934,7 @@ public final class Launcher extends Activity case KeyEvent.KEYCODE_HOME: return true; case KeyEvent.KEYCODE_VOLUME_DOWN: - if (doesFileExist(DUMP_STATE_PROPERTY)) { + if (isPropertyEnabled(DUMP_STATE_PROPERTY)) { dumpState(); return true; } @@ -2464,6 +2475,7 @@ public final class Launcher extends Activity } // Now a part of LauncherModel.Callbacks. Used to reorder loading steps. + @Override public boolean isAllAppsVisible() { return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE); } @@ -2471,7 +2483,7 @@ public final class Launcher extends Activity /** * Helper method for the cameraZoomIn/cameraZoomOut animations * @param view The view being animated - * + * @param scaleFactor The scale factor used for the zoom */ private void setPivotsForZoom(View view) { view.setPivotX(view.getWidth() / 2.0f); @@ -2493,6 +2505,11 @@ public final class Launcher extends Activity updateWallpaperVisibility(visible); } + private void setWorkspaceBackground(boolean workspace) { + mLauncherView.setBackground(workspace ? + mWorkspaceBackgroundDrawable : mBlackBackgroundDrawable); + } + void updateWallpaperVisibility(boolean visible) { int wpflags = visible && mWallpaperVisible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0; int curflags = getWindow().getAttributes().flags @@ -2500,6 +2517,7 @@ public final class Launcher extends Activity if (wpflags != curflags) { getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER); } + setWorkspaceBackground(visible); } private void updateFullscreenMode(boolean enable) { @@ -3817,6 +3835,11 @@ public final class Launcher extends Activity return oriMap[(d.getRotation() + indexOffset) % 4]; } + public boolean isRotationEnabled() { + boolean enableRotation = sForceEnableRotation || + getResources().getBoolean(R.bool.allow_rotation); + return enableRotation; + } public void lockScreenOrientation() { if (mAutoRotate) { setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources() @@ -3878,8 +3901,9 @@ public final class Launcher extends Activity } private void dismissCling(final Cling cling, final String flag, int duration) { - if (cling != null && cling.getVisibility() == View.VISIBLE) { - cling.dismiss(); + // To catch cases where siblings of top-level views are made invisible, just check whether + // the cling is directly set to GONE before dismissing it. + if (cling != null && cling.getVisibility() != View.GONE) { ObjectAnimator anim = LauncherAnimUtils.ofFloat(cling, "alpha", 0f); anim.setDuration(duration); anim.addListener(new AnimatorListenerAdapter() { diff --git a/src/com/cyanogenmod/trebuchet/LauncherAppWidgetInfo.java b/src/com/cyanogenmod/trebuchet/LauncherAppWidgetInfo.java index 7f6a46c5b..0ab5dae84 100644 --- a/src/com/cyanogenmod/trebuchet/LauncherAppWidgetInfo.java +++ b/src/com/cyanogenmod/trebuchet/LauncherAppWidgetInfo.java @@ -19,7 +19,6 @@ package com.cyanogenmod.trebuchet; import android.appwidget.AppWidgetHostView; import android.content.ComponentName; import android.content.ContentValues; -import android.os.Build; /** * Represents a widget (either instantiated or about to be) in the Launcher. diff --git a/src/com/cyanogenmod/trebuchet/PagedView.java b/src/com/cyanogenmod/trebuchet/PagedView.java index 667aab27a..42ae9687e 100644 --- a/src/com/cyanogenmod/trebuchet/PagedView.java +++ b/src/com/cyanogenmod/trebuchet/PagedView.java @@ -319,13 +319,15 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc * the previous tab page. */ protected void updateCurrentPageScroll() { - int newXY = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage); - scrollTo(!mVertical ? newXY : 0, mVertical ? newXY : 0); - if (!mVertical) { - mScroller.setFinalX(newXY); - } else { - mScroller.setFinalY(newXY); - } + // If the current page is invalid, just reset the scroll position to zero + int newX = 0; + if (0 <= mCurrentPage && mCurrentPage < getPageCount()) { + int offset = getChildOffset(mCurrentPage); + int relOffset = getRelativeChildOffset(mCurrentPage); + newX = offset - relOffset; + } + scrollTo(newX, 0); + mScroller.setFinalX(newX); mScroller.forceFinished(true); } |