diff options
30 files changed, 654 insertions, 930 deletions
diff --git a/res/layout/search_drop_target_bar.xml b/res/layout/search_drop_target_bar.xml index e383d74c0..69f42bb4c 100644 --- a/res/layout/search_drop_target_bar.xml +++ b/res/layout/search_drop_target_bar.xml @@ -44,26 +44,26 @@ style="@style/DropTargetButtonContainer" android:layout_weight="1" > - <!-- Uninstall target --> + <!-- Info target --> - <com.android.launcher3.UninstallDropTarget - android:id="@+id/uninstall_target_text" + <com.android.launcher3.InfoDropTarget + android:id="@+id/info_target_text" style="@style/DropTargetButton" - android:drawableStart="@drawable/uninstall_target_selector" - android:text="@string/delete_target_uninstall_label" /> + android:drawableStart="@drawable/info_target_selector" + android:text="@string/info_target_label" /> </FrameLayout> <FrameLayout style="@style/DropTargetButtonContainer" android:layout_weight="1" > - <!-- Info target --> + <!-- Uninstall target --> - <com.android.launcher3.InfoDropTarget - android:id="@+id/info_target_text" + <com.android.launcher3.UninstallDropTarget + android:id="@+id/uninstall_target_text" style="@style/DropTargetButton" - android:drawableStart="@drawable/info_target_selector" - android:text="@string/info_target_label" /> + android:drawableStart="@drawable/uninstall_target_selector" + android:text="@string/delete_target_uninstall_label" /> </FrameLayout> </LinearLayout> diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml index 50294c035..64ddea1ae 100644 --- a/res/layout/widget_cell.xml +++ b/res/layout/widget_cell.xml @@ -47,7 +47,7 @@ android:fadingEdge="horizontal" android:textColor="#FFFFFFFF" - android:textSize="12sp" + android:textSize="16sp" android:textAlignment="viewStart" android:fontFamily="sans-serif-condensed" android:shadowRadius="2.0" @@ -64,7 +64,7 @@ android:layout_weight="0" android:gravity="start" android:textColor="#FFFFFFFF" - android:textSize="12sp" + android:textSize="16sp" android:textAlignment="viewStart" android:fontFamily="sans-serif-condensed" android:shadowRadius="2.0" diff --git a/res/values/strings.xml b/res/values/strings.xml index 52306e30e..a68f53a91 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -77,7 +77,7 @@ <!-- Search bar text in the apps view. [CHAR_LIMIT=50] --> <string name="apps_view_search_bar_hint">Search Apps</string> <!-- Loading apps text. [CHAR_LIMIT=50] --> - <string name="loading_apps_message">Loading Apps...</string> + <string name="loading_apps_message">Loading Apps…</string> <!-- No-search-results text. [CHAR_LIMIT=50] --> <string name="apps_view_no_search_results">No Apps found matching \"<xliff:g id="query" example="Android">%1$s</xliff:g>\"</string> @@ -91,8 +91,6 @@ <string name="rename_action">OK</string> <!-- Buttons in Rename folder dialog box --> <string name="cancel_action">Cancel</string> - <!-- Label for button to sort folder contents. [CHAR_LIMIT=10] --> - <string name="sort_alphabetical">A-Z</string> <!-- Shortcuts --> <skip /> @@ -309,13 +307,13 @@ s --> <!-- Strings for accessibility actions --> <!-- Accessibility action to add an app to workspace. [CHAR_LIMIT=30] [DO NOT TRANSLATE] --> - <string name="action_add_to_workspace">Add To Workspace</string> + <string name="action_add_to_workspace">Add to workspace</string> <!-- Accessibility confirmation for item added to workspace [DO NOT TRANSLATE] --> <string name="item_added_to_workspace">Item added to workspace</string> <!-- Accessibility confirmation for item removed [DO NOT TRANSLATE] --> - <string name="item_removed_from_workspace">Item removed from workspace</string> + <string name="item_removed">Item removed</string> <!-- Accessibility action to move an item on the workspace. [CHAR_LIMIT=30] [DO NOT TRANSLATE] --> <string name="action_move">Move Item</string> diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index 240250750..3c698c014 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -349,7 +349,7 @@ public class AppWidgetResizeFrame extends FrameLayout { mTmpRect.right, mTmpRect.bottom); } - static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) { + public static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) { if (rect == null) { rect = new Rect(); } diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java index 16244ee35..f8897128e 100644 --- a/src/com/android/launcher3/AppsContainerRecyclerView.java +++ b/src/com/android/launcher3/AppsContainerRecyclerView.java @@ -156,6 +156,10 @@ public class AppsContainerRecyclerView extends RecyclerView handleTouchEvent(ev); } + public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { + // Do nothing + } + /** * Handles the touch event and determines whether to show the fast scroller (or updates it if * it is already showing). diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java index 5b399087a..fb49df5df 100644 --- a/src/com/android/launcher3/ButtonDropTarget.java +++ b/src/com/android/launcher3/ButtonDropTarget.java @@ -26,18 +26,19 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.util.AttributeSet; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; import android.widget.TextView; -import com.android.launcher3.R; import com.android.launcher3.util.Thunk; /** * Implements a DropTarget. */ -public abstract class ButtonDropTarget extends TextView implements DropTarget, DragController.DragListener { +public abstract class ButtonDropTarget extends TextView + implements DropTarget, DragController.DragListener, OnClickListener { private static int DRAG_VIEW_DROP_DURATION = 285; @@ -256,4 +257,18 @@ public abstract class ButtonDropTarget extends TextView implements DropTarget, D public void getLocationInDragLayer(int[] loc) { mLauncher.getDragLayer().getLocationInDragLayer(this, loc); } + + public void enableAccessibleDrag(boolean enable) { + setOnClickListener(enable ? this : null); + } + + protected String getAccessibilityDropConfirmation() { + return null; + } + + @Override + public void onClick(View v) { + LauncherAppState.getInstance().getAccessibilityDelegate() + .handleAccessibleDrop(this, null, getAccessibilityDropConfirmation()); + } } diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index f4afb954d..85653bef7 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -48,9 +48,7 @@ import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; -import android.view.animation.Animation; import android.view.animation.DecelerateInterpolator; -import android.view.animation.LayoutAnimationController; import com.android.launcher3.FolderIcon.FolderRingAnimator; import com.android.launcher3.LauncherAccessibilityDelegate.DragType; @@ -121,7 +119,6 @@ public class CellLayout extends ViewGroup { // If we're actively dragging something over this screen, mIsDragOverlapping is true private boolean mIsDragOverlapping = false; - boolean mUseActiveGlowBackground = false; // These arrays are used to implement the drag visualization on x-large screens. // They are used as circular arrays, indexed by mDragOutlineCurrent. @@ -557,7 +554,7 @@ public class CellLayout extends ViewGroup { Resources res = getContext().getResources(); View child = getChildAt(x, y); if (child == null || child == dragInfo.item) { - return res.getString(R.string.move_to_empty_cell, x, y); + return res.getString(R.string.move_to_empty_cell, x + 1, y + 1); } else { ItemInfo info = (ItemInfo) child.getTag(); if (info instanceof AppInfo || info instanceof ShortcutInfo) { @@ -684,10 +681,6 @@ public class CellLayout extends ViewGroup { } } - void setUseActiveGlowBackground(boolean use) { - mUseActiveGlowBackground = use; - } - void disableBackground() { mDrawBackground = false; } @@ -703,7 +696,6 @@ public class CellLayout extends ViewGroup { void setIsDragOverlapping(boolean isDragOverlapping) { if (mIsDragOverlapping != isDragOverlapping) { mIsDragOverlapping = isDragOverlapping; - setUseActiveGlowBackground(mIsDragOverlapping); invalidate(); } } @@ -722,7 +714,7 @@ public class CellLayout extends ViewGroup { if (mDrawBackground && mBackgroundAlpha > 0.0f) { Drawable bg; - if (mUseActiveGlowBackground) { + if (mIsDragOverlapping) { // In the mini case, we draw the active_glow bg *over* the active background bg = mActiveGlowBackground; } else { @@ -906,7 +898,7 @@ public class CellLayout extends ViewGroup { } public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params, - boolean markCells, boolean inLayout) { + boolean markCells) { final LayoutParams lp = params; // Hotseat icons - remove text @@ -927,7 +919,7 @@ public class CellLayout extends ViewGroup { if (lp.cellVSpan < 0) lp.cellVSpan = mCountY; child.setId(childId); - mShortcutsAndWidgets.addView(child, index, lp, inLayout); + mShortcutsAndWidgets.addView(child, index, lp); if (markCells) markCellsAsOccupiedForView(child); @@ -936,11 +928,6 @@ public class CellLayout extends ViewGroup { return false; } - public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params, - boolean markCells) { - return addViewToCellLayout(child, index, childId, params, markCells, false); - } - @Override public void removeAllViews() { clearOccupiedCells(); @@ -955,10 +942,6 @@ public class CellLayout extends ViewGroup { } } - public void removeViewWithoutMarkingCells(View view) { - mShortcutsAndWidgets.removeView(view); - } - @Override public void removeView(View view) { markCellsAsUnoccupiedForView(view); @@ -1088,9 +1071,7 @@ public class CellLayout extends ViewGroup { public float getDistanceFromCell(float x, float y, int[] cell) { cellToCenterPoint(cell[0], cell[1], mTmpPoint); - float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) + - Math.pow(y - mTmpPoint[1], 2)); - return distance; + return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]); } int getCellWidth() { @@ -1109,28 +1090,6 @@ public class CellLayout extends ViewGroup { return mHeightGap; } - Rect getContentRect(Rect r) { - if (r == null) { - r = new Rect(); - } - int left = getPaddingLeft(); - int top = getPaddingTop(); - int right = left + getWidth() - getPaddingLeft() - getPaddingRight(); - int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom(); - r.set(left, top, right, bottom); - return r; - } - - /** Return a rect that has the cellWidth/cellHeight (left, top), and - * widthGap/heightGap (right, bottom) */ - static void getMetrics(Rect metrics, int paddedMeasureWidth, - int paddedMeasureHeight, int countX, int countY) { - LauncherAppState app = LauncherAppState.getInstance(); - DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); - metrics.set(grid.calculateCellWidth(paddedMeasureWidth, countX), - grid.calculateCellHeight(paddedMeasureHeight, countY), 0, 0); - } - public void setFixedSize(int width, int height) { mFixedWidth = width; mFixedHeight = height; @@ -1246,7 +1205,6 @@ public class CellLayout extends ViewGroup { } public void setBackgroundAlphaMultiplier(float multiplier) { - if (mBackgroundAlphaMultiplier != multiplier) { mBackgroundAlphaMultiplier = multiplier; invalidate(); @@ -1360,36 +1318,6 @@ public class CellLayout extends ViewGroup { return false; } - /** - * Estimate where the top left cell of the dragged item will land if it is dropped. - * - * @param originX The X value of the top left corner of the item - * @param originY The Y value of the top left corner of the item - * @param spanX The number of horizontal cells that the item spans - * @param spanY The number of vertical cells that the item spans - * @param result The estimated drop cell X and Y. - */ - void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) { - final int countX = mCountX; - final int countY = mCountY; - - // pointToCellRounded takes the top left of a cell but will pad that with - // cellWidth/2 and cellHeight/2 when finding the matching cell - pointToCellRounded(originX, originY, result); - - // If the item isn't fully on this screen, snap to the edges - int rightOverhang = result[0] + spanX - countX; - if (rightOverhang > 0) { - result[0] -= rightOverhang; // Snap to right - } - result[0] = Math.max(0, result[0]); // Snap to left - int bottomOverhang = result[1] + spanY - countY; - if (bottomOverhang > 0) { - result[1] -= bottomOverhang; // Snap to bottom - } - result[1] = Math.max(0, result[1]); // Snap to top - } - void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX, int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) { final int oldDragCellX = mDragCell[0]; @@ -1473,9 +1401,8 @@ public class CellLayout extends ViewGroup { * @return The X, Y cell of a vacant area that can contain this object, * nearest the requested location. */ - int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, - int[] result) { - return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result); + int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) { + return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null); } /** @@ -1495,30 +1422,10 @@ public class CellLayout extends ViewGroup { */ int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, int[] result, int[] resultSpan) { - return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, + return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true, result, resultSpan); } - /** - * Find a vacant area that will fit the given bounds nearest the requested - * cell location. Uses Euclidean distance to score multiple vacant areas. - * - * @param pixelX The X location at which you want to search for a vacant area. - * @param pixelY The Y location at which you want to search for a vacant area. - * @param spanX Horizontal span of the object. - * @param spanY Vertical span of the object. - * @param ignoreOccupied If true, the result can be an occupied cell - * @param result Array in which to place the result, or null (in which case a new array will - * be allocated) - * @return The X, Y cell of a vacant area that can contain this object, - * nearest the requested location. - */ - int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView, - boolean ignoreOccupied, int[] result) { - return findNearestArea(pixelX, pixelY, spanX, spanY, - spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied); - } - private final Stack<Rect> mTempRectStack = new Stack<Rect>(); private void lazyInitTempRectStack() { if (mTempRectStack.isEmpty()) { @@ -1550,12 +1457,9 @@ public class CellLayout extends ViewGroup { * @return The X, Y cell of a vacant area that can contain this object, * nearest the requested location. */ - int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, - View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan, - boolean[][] occupied) { + private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, + int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) { lazyInitTempRectStack(); - // mark space take by ignoreView as available (method checks if ignoreView is null) - markCellsAsUnoccupiedForView(ignoreView, occupied); // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds // to the center of the item, but we are searching based on the top-left cell, so @@ -1586,7 +1490,7 @@ public class CellLayout extends ViewGroup { // First, let's see if this thing fits anywhere for (int i = 0; i < minSpanX; i++) { for (int j = 0; j < minSpanY; j++) { - if (occupied[x + i][y + j]) { + if (mOccupied[x + i][y + j]) { continue inner; } } @@ -1603,7 +1507,7 @@ public class CellLayout extends ViewGroup { while (!(hitMaxX && hitMaxY)) { if (incX && !hitMaxX) { for (int j = 0; j < ySize; j++) { - if (x + xSize > countX -1 || occupied[x + xSize][y + j]) { + if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) { // We can't move out horizontally hitMaxX = true; } @@ -1613,7 +1517,7 @@ public class CellLayout extends ViewGroup { } } else if (!hitMaxY) { for (int i = 0; i < xSize; i++) { - if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) { + if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) { // We can't move out vertically hitMaxY = true; } @@ -1646,8 +1550,7 @@ public class CellLayout extends ViewGroup { } } validRegions.push(currentRect); - double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) - + Math.pow(cellXY[1] - pixelY, 2)); + double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY); if ((distance <= bestDistance && !contained) || currentRect.contains(bestRect)) { @@ -1662,8 +1565,6 @@ public class CellLayout extends ViewGroup { } } } - // re-mark space taken by ignoreView as occupied - markCellsAsOccupiedForView(ignoreView, occupied); // Return -1, -1 if no suitable location found if (bestDistance == Double.MAX_VALUE) { @@ -1717,8 +1618,7 @@ public class CellLayout extends ViewGroup { } } - float distance = (float) - Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY)); + float distance = (float) Math.hypot(x - cellX, y - cellY); int[] curDirection = mTmpPoint; computeDirectionVector(x - cellX, y - cellY, curDirection); // The direction score is just the dot product of the two candidate direction @@ -2334,7 +2234,7 @@ public class CellLayout extends ViewGroup { } } - 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, ItemConfiguration solution) { // Copy the current state into the solution. This solution will be manipulated as necessary. @@ -2623,7 +2523,7 @@ public class CellLayout extends ViewGroup { mLauncher.getWorkspace().updateItemLocationsInDatabase(this); } - public void setUseTempCoords(boolean useTempCoords) { + private void setUseTempCoords(boolean useTempCoords) { int childCount = mShortcutsAndWidgets.getChildCount(); for (int i = 0; i < childCount; i++) { LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams(); @@ -2631,11 +2531,11 @@ public class CellLayout extends ViewGroup { } } - ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY, + private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, View dragView, ItemConfiguration solution) { int[] result = new int[2]; int[] resultSpan = new int[2]; - findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result, + findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result, resultSpan); if (result[0] >= 0 && result[1] >= 0) { copyCurrentStateToSolution(solution, false); @@ -2952,45 +2852,6 @@ public class CellLayout extends ViewGroup { } /** - * Find a vacant area that will fit the given bounds nearest the requested - * cell location. Uses Euclidean distance to score multiple vacant areas. - * - * @param pixelX The X location at which you want to search for a vacant area. - * @param pixelY The Y location at which you want to search for a vacant area. - * @param spanX Horizontal span of the object. - * @param spanY Vertical span of the object. - * @param ignoreView Considers space occupied by this view as unoccupied - * @param result Previously returned value to possibly recycle. - * @return The X, Y cell of a vacant area that can contain this object, - * nearest the requested location. - */ - int[] findNearestVacantArea( - int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) { - return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result); - } - - /** - * Find a vacant area that will fit the given bounds nearest the requested - * cell location. Uses Euclidean distance to score multiple vacant areas. - * - * @param pixelX The X location at which you want to search for a vacant area. - * @param pixelY The Y location at which you want to search for a vacant area. - * @param minSpanX The minimum horizontal span required - * @param minSpanY The minimum vertical span required - * @param spanX Horizontal span of the object. - * @param spanY Vertical span of the object. - * @param ignoreView Considers space occupied by this view as unoccupied - * @param result Previously returned value to possibly recycle. - * @return The X, Y cell of a vacant area that can contain this object, - * nearest the requested location. - */ - int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, - int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) { - return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true, - result, resultSpan, mOccupied); - } - - /** * Find a starting cell position that will fit the given bounds nearest the requested * cell location. Uses Euclidean distance to score multiple vacant areas. * @@ -3003,9 +2864,8 @@ public class CellLayout extends ViewGroup { * @return The X, Y cell of a vacant area that can contain this object, * nearest the requested location. */ - int[] findNearestArea( - int pixelX, int pixelY, int spanX, int spanY, int[] result) { - return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result); + int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) { + return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null); } boolean existsEmptyCell() { @@ -3026,103 +2886,32 @@ public class CellLayout extends ViewGroup { * @return True if a vacant cell of the specified dimension was found, false otherwise. */ public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) { - return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied); - } - - /** - * Like above, but ignores any cells occupied by the item "ignoreView" - * - * @param cellXY The array that will contain the position of a vacant cell if such a cell - * can be found. - * @param spanX The horizontal span of the cell we want to find. - * @param spanY The vertical span of the cell we want to find. - * @param ignoreView The home screen item we should treat as not occupying any space - * @return - */ - boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) { - return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, - ignoreView, mOccupied); - } - - /** - * Like above, but if intersectX and intersectY are not -1, then this method will try to - * return coordinates for rectangles that contain the cell [intersectX, intersectY] - * - * @param spanX The horizontal span of the cell we want to find. - * @param spanY The vertical span of the cell we want to find. - * @param ignoreView The home screen item we should treat as not occupying any space - * @param intersectX The X coordinate of the cell that we should try to overlap - * @param intersectX The Y coordinate of the cell that we should try to overlap - * - * @return True if a vacant cell of the specified dimension was found, false otherwise. - */ - boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY, - int intersectX, int intersectY) { - return findCellForSpanThatIntersectsIgnoring( - cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied); - } - - /** - * The superset of the above two methods - */ - boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY, - int intersectX, int intersectY, View ignoreView, boolean occupied[][]) { - // mark space take by ignoreView as available (method checks if ignoreView is null) - markCellsAsUnoccupiedForView(ignoreView, occupied); - boolean foundCell = false; - while (true) { - int startX = 0; - if (intersectX >= 0) { - startX = Math.max(startX, intersectX - (spanX - 1)); - } - int endX = mCountX - (spanX - 1); - if (intersectX >= 0) { - endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0)); - } - int startY = 0; - if (intersectY >= 0) { - startY = Math.max(startY, intersectY - (spanY - 1)); - } - int endY = mCountY - (spanY - 1); - if (intersectY >= 0) { - endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0)); - } - - for (int y = startY; y < endY && !foundCell; y++) { - inner: - for (int x = startX; x < endX; x++) { - for (int i = 0; i < spanX; i++) { - for (int j = 0; j < spanY; j++) { - if (occupied[x + i][y + j]) { - // small optimization: we can skip to after the column we just found - // an occupied cell - x += i; - continue inner; - } + final int endX = mCountX - (spanX - 1); + 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; } - } - if (intersectX == -1 && intersectY == -1) { + if (cellXY != null) { + cellXY[0] = x; + cellXY[1] = y; + } + foundCell = true; break; - } else { - // if we failed to find anything, try again but without any requirements of - // intersecting - intersectX = -1; - intersectY = -1; - continue; } } - // re-mark space taken by ignoreView as occupied - markCellsAsOccupiedForView(ignoreView, occupied); return foundCell; } @@ -3232,13 +3021,6 @@ public class CellLayout extends ViewGroup { return result; } - public int[] cellSpansToSize(int hSpans, int vSpans) { - int[] size = new int[2]; - size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap; - size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap; - return size; - } - /** * Calculate the grid spans needed to fit given item */ @@ -3262,44 +3044,6 @@ public class CellLayout extends ViewGroup { info.spanY = spans[1]; } - /** - * 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 boolean getVacantCell(int[] vacant, int spanX, int spanY) { - - return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied); - } - - 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; - } - private void clearOccupiedCells() { for (int x = 0; x < mCountX; x++) { for (int y = 0; y < mCountY; y++) { @@ -3308,27 +3052,16 @@ out: for (int i = x; i < x + spanX; i++) { } } - public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) { - markCellsAsUnoccupiedForView(view); - markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true); - } - public void markCellsAsOccupiedForView(View view) { - markCellsAsOccupiedForView(view, mOccupied); - } - public void markCellsAsOccupiedForView(View view, boolean[][] occupied) { if (view == null || view.getParent() != mShortcutsAndWidgets) return; LayoutParams lp = (LayoutParams) view.getLayoutParams(); - markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true); + markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true); } public void markCellsAsUnoccupiedForView(View view) { - markCellsAsUnoccupiedForView(view, mOccupied); - } - public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) { if (view == null || view.getParent() != mShortcutsAndWidgets) return; LayoutParams lp = (LayoutParams) view.getLayoutParams(); - markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false); + markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false); } private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied, @@ -3374,17 +3107,6 @@ out: for (int i = x; i < x + spanX; i++) { return new CellLayout.LayoutParams(p); } - public static class CellLayoutAnimationController extends LayoutAnimationController { - public CellLayoutAnimationController(Animation animation, float delay) { - super(animation, delay); - } - - @Override - protected long getDelayForView(View view) { - return (int) (Math.random() * 150); - } - } - public static class LayoutParams extends ViewGroup.MarginLayoutParams { /** * Horizontal location of the item in the grid. diff --git a/src/com/android/launcher3/DeferredHandler.java b/src/com/android/launcher3/DeferredHandler.java index eb7c26a28..a43ab6723 100644 --- a/src/com/android/launcher3/DeferredHandler.java +++ b/src/com/android/launcher3/DeferredHandler.java @@ -20,12 +20,10 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; -import android.util.Pair; import com.android.launcher3.util.Thunk; import java.util.LinkedList; -import java.util.ListIterator; /** * Queue of things to run on a looper thread. Items posted with {@link #post} will not @@ -35,20 +33,18 @@ import java.util.ListIterator; * This class is fifo. */ public class DeferredHandler { - @Thunk LinkedList<Pair<Runnable, Integer>> mQueue = new LinkedList<Pair<Runnable, Integer>>(); + @Thunk LinkedList<Runnable> mQueue = new LinkedList<>(); private MessageQueue mMessageQueue = Looper.myQueue(); private Impl mHandler = new Impl(); @Thunk class Impl extends Handler implements MessageQueue.IdleHandler { public void handleMessage(Message msg) { - Pair<Runnable, Integer> p; Runnable r; synchronized (mQueue) { if (mQueue.size() == 0) { return; } - p = mQueue.removeFirst(); - r = p.first; + r = mQueue.removeFirst(); } r.run(); synchronized (mQueue) { @@ -79,11 +75,8 @@ public class DeferredHandler { /** Schedule runnable to run after everything that's on the queue right now. */ public void post(Runnable runnable) { - post(runnable, 0); - } - public void post(Runnable runnable, int type) { synchronized (mQueue) { - mQueue.add(new Pair<Runnable, Integer>(runnable, type)); + mQueue.add(runnable); if (mQueue.size() == 1) { scheduleNextLocked(); } @@ -92,31 +85,10 @@ public class DeferredHandler { /** Schedule runnable to run when the queue goes idle. */ public void postIdle(final Runnable runnable) { - postIdle(runnable, 0); - } - public void postIdle(final Runnable runnable, int type) { - post(new IdleRunnable(runnable), type); - } - - public void cancelRunnable(Runnable runnable) { - synchronized (mQueue) { - while (mQueue.remove(runnable)) { } - } - } - public void cancelAllRunnablesOfType(int type) { - synchronized (mQueue) { - ListIterator<Pair<Runnable, Integer>> iter = mQueue.listIterator(); - Pair<Runnable, Integer> p; - while (iter.hasNext()) { - p = iter.next(); - if (p.second == type) { - iter.remove(); - } - } - } + post(new IdleRunnable(runnable)); } - public void cancel() { + public void cancelAll() { synchronized (mQueue) { mQueue.clear(); } @@ -124,20 +96,19 @@ public class DeferredHandler { /** Runs all queued Runnables from the calling thread. */ public void flush() { - LinkedList<Pair<Runnable, Integer>> queue = new LinkedList<Pair<Runnable, Integer>>(); + LinkedList<Runnable> queue = new LinkedList<>(); synchronized (mQueue) { queue.addAll(mQueue); mQueue.clear(); } - for (Pair<Runnable, Integer> p : queue) { - p.first.run(); + for (Runnable r : queue) { + r.run(); } } void scheduleNextLocked() { if (mQueue.size() > 0) { - Pair<Runnable, Integer> p = mQueue.getFirst(); - Runnable peek = p.first; + Runnable peek = mQueue.getFirst(); if (peek instanceof IdleRunnable) { mMessageQueue.addIdleHandler(mHandler); } else { diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index aa3e66c09..e741b9787 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -29,7 +29,6 @@ import android.view.ViewConfiguration; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; -import com.android.launcher3.R; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.WidgetsContainerView; @@ -59,13 +58,15 @@ public class DeleteDropTarget extends ButtonDropTarget { setDrawable(R.drawable.remove_target_selector); } - public static boolean willAcceptDrop(DragSource source, Object info) { - return (info instanceof ItemInfo) && source.supportsDeleteDropTarget(); + public static boolean supportsDrop(Object info) { + return (info instanceof ShortcutInfo) + || (info instanceof LauncherAppWidgetInfo) + || (info instanceof FolderInfo); } @Override protected boolean supportsDrop(DragSource source, Object info) { - return willAcceptDrop(source, info); + return source.supportsDeleteDropTarget() && supportsDrop(info); } @Override @@ -304,4 +305,9 @@ public class DeleteDropTarget extends ButtonDropTarget { dragLayer.animateView(d.dragView, updateCb, duration, tInterpolator, onAnimationEndRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null); } + + @Override + protected String getAccessibilityDropConfirmation() { + return getResources().getString(R.string.item_removed); + } } diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 40998a52a..deb807501 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -460,8 +460,7 @@ public class DeviceProfile { } @Thunk float dist(PointF p0, PointF p1) { - return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) + - (p1.y-p0.y)*(p1.y-p0.y)); + return (float) Math.hypot(p1.x - p0.x, p1.y - p0.y); } private float weight(PointF a, PointF b, diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java index b24608cb1..3b21c2b55 100644 --- a/src/com/android/launcher3/DragController.java +++ b/src/com/android/launcher3/DragController.java @@ -462,8 +462,7 @@ public class DragController { mLastTouchUpTime = System.currentTimeMillis(); if (mDragging) { PointF vec = isFlingingToDelete(mDragObject.dragSource); - if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragSource, - mDragObject.dragInfo)) { + if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) { vec = null; } if (vec != null) { @@ -514,8 +513,7 @@ public class DragController { checkTouchMove(dropTarget); // Check if we are hovering over the scroll areas - mDistanceSinceScroll += - Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2)); + mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y); mLastTouch[0] = x; mLastTouch[1] = y; checkScrollState(x, y); @@ -617,7 +615,7 @@ public class DragController { if (mDragging) { PointF vec = isFlingingToDelete(mDragObject.dragSource); - if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragSource, mDragObject.dragInfo)) { + if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) { vec = null; } if (vec != null) { diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java index ab2e094cb..91f97fa4a 100644 --- a/src/com/android/launcher3/DragLayer.java +++ b/src/com/android/launcher3/DragLayer.java @@ -657,8 +657,7 @@ public class DragLayer extends InsettableFrameLayout { final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) { // Calculate the duration of the animation based on the object's distance - final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) + - Math.pow(to.top - from.top, 2)); + final float dist = (float) Math.hypot(to.left - from.left, to.top - from.top); final Resources res = getResources(); final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java index c35ce944f..03a9019e8 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/Folder.java @@ -83,12 +83,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList public static final int SCROLL_HINT_DURATION = DragController.SCROLL_DELAY; /** - * Time in milliseconds for which an icon sticks to the target position - * in case of a sorted folder. - */ - private static final int SORTED_STICKY_REORDER_DELAY = 1500; - - /** * Fraction of icon width which behave as scroll region. */ private static final float ICON_OVERSCROLL_WIDTH_FACTOR = 0.45f; @@ -417,7 +411,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList if (!(getParent() instanceof DragLayer)) return; mContent.completePendingPageChanges(); - if (!(mDragInProgress && mContent.mIsSorted)) { + if (!mDragInProgress) { // Open on the first page. mContent.snapToPageImmediately(0); } @@ -459,7 +453,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX()); int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY()); - float radius = (float) Math.sqrt(rx * rx + ry * ry); + float radius = (float) Math.hypot(rx, ry); AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); Animator reveal = LauncherAnimUtils.createCircularReveal(this, (int) getPivotX(), (int) getPivotY(), 0, radius); @@ -533,12 +527,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mIsExternalDrag = true; mDragInProgress = true; - if (mContent.mIsSorted) { - mScrollPauseAlarm.setOnAlarmListener(null); - mScrollPauseAlarm.cancelAlarm(); - mScrollPauseAlarm.setAlarm(SORTED_STICKY_REORDER_DELAY); - } - // Since this folder opened by another controller, it might not get onDrop or // onDropComplete. Perform cleanup once drag-n-drop ends. mDragController.addDragListener(this); @@ -745,9 +733,18 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList replaceFolderWithFinalItem(); } } else { - rearrangeChildren(); // The drag failed, we need to return the item to the folder + ShortcutInfo info = (ShortcutInfo) d.dragInfo; + View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info) + ? mCurrentDragView : mContent.createNewView(info); + ArrayList<View> views = getItemsInReadingOrder(); + views.add(info.rank, icon); + mContent.arrangeChildren(views, views.size()); + mItemsInvalidated = true; + + mSuppressOnAdd = true; mFolderIcon.onDrop(d); + mSuppressOnAdd = false; } if (target != this) { diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index 80b156413..69b2ae78c 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -33,6 +33,7 @@ public class FolderInfo extends ItemInfo { /** * The folder is locked in sorted mode + * @deprecated */ public static final int FLAG_ITEMS_SORTED = 0x00000001; diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java index 617489271..c68ef72b3 100644 --- a/src/com/android/launcher3/FolderPagedView.java +++ b/src/com/android/launcher3/FolderPagedView.java @@ -20,23 +20,16 @@ import android.annotation.SuppressLint; import android.content.Context; import android.util.AttributeSet; import android.util.Log; -import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.animation.DecelerateInterpolator; -import android.view.animation.Interpolator; -import android.view.animation.OvershootInterpolator; -import android.widget.Switch; import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener; import com.android.launcher3.PageIndicator.PageMarkerResources; import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.util.Thunk; -import java.text.Collator; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -47,17 +40,10 @@ public class FolderPagedView extends PagedView { private static final boolean ALLOW_FOLDER_SCROLL = true; - // To enable this flag, user_folder.xml needs to be modified to add sort button. - private static final boolean ALLOW_ITEM_SORTING = false; - private static final int REORDER_ANIMATION_DURATION = 230; private static final int START_VIEW_REORDER_DELAY = 30; private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f; - private static final int SPAN_TO_PAGE_DURATION = 350; - private static final int SORT_ANIM_HIDE_DURATION = 130; - private static final int SORT_ANIM_SHOW_DURATION = 160; - /** * Fraction of the width to scroll when showing the next page hint. */ @@ -87,13 +73,8 @@ public class FolderPagedView extends PagedView { private FocusIndicatorView mFocusIndicatorView; private PagedFolderKeyEventListener mKeyListener; - private View mSortButton; - private Switch mSortSwitch; private View mPageIndicator; - private boolean mSortOperationPending; - boolean mIsSorted; - public FolderPagedView(Context context, AttributeSet attrs) { super(context, attrs); LauncherAppState app = LauncherAppState.getInstance(); @@ -121,132 +102,6 @@ public class FolderPagedView extends PagedView { mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator); mKeyListener = new PagedFolderKeyEventListener(folder); mPageIndicator = folder.findViewById(R.id.folder_page_indicator); - - if (ALLOW_ITEM_SORTING) { - // Initialize {@link #mSortSwitch} and {@link #mSortButton}. - } - } - - /** - * Called when sort button is clicked. - */ - private void onSortClicked() { - if (mSortOperationPending) { - return; - } - if (mIsSorted) { - setIsSorted(false, true); - } else { - mSortOperationPending = true; - doSort(); - } - } - - private void setIsSorted(boolean isSorted, boolean saveChanges) { - mIsSorted = isSorted; - if (ALLOW_ITEM_SORTING) { - mSortSwitch.setChecked(isSorted); - mFolder.mInfo.setOption(FolderInfo.FLAG_ITEMS_SORTED, isSorted, - saveChanges ? mFolder.mLauncher : null); - } - } - - /** - * Sorts the contents of the folder and animates the icons on the first page to reflect - * the changes. - * Steps: - * 1. Scroll to first page - * 2. Sort all icons in one go - * 3. Re-apply the old IconInfos on the first page (so that there is no instant change) - * 4. Animate each view individually to reflect the new icon. - */ - private void doSort() { - if (!mSortOperationPending) { - return; - } - if (getNextPage() != 0) { - snapToPage(0, SPAN_TO_PAGE_DURATION, new DecelerateInterpolator()); - return; - } - - mSortOperationPending = false; - ShortcutInfo[][] oldItems = new ShortcutInfo[mGridCountX][mGridCountY]; - CellLayout currentPage = getCurrentCellLayout(); - for (int x = 0; x < mGridCountX; x++) { - for (int y = 0; y < mGridCountY; y++) { - View v = currentPage.getChildAt(x, y); - if (v != null) { - oldItems[x][y] = (ShortcutInfo) v.getTag(); - } - } - } - - ArrayList<View> views = new ArrayList<View>(mFolder.getItemsInReadingOrder()); - Collections.sort(views, new ViewComparator()); - arrangeChildren(views, views.size()); - - int delay = 0; - float delayAmount = START_VIEW_REORDER_DELAY; - final Interpolator hideInterpolator = new DecelerateInterpolator(2); - final Interpolator showInterpolator = new OvershootInterpolator(0.8f); - - currentPage = getCurrentCellLayout(); - for (int x = 0; x < mGridCountX; x++) { - for (int y = 0; y < mGridCountY; y++) { - final BubbleTextView v = (BubbleTextView) currentPage.getChildAt(x, y); - if (v != null) { - final ShortcutInfo info = (ShortcutInfo) v.getTag(); - final Runnable clearPending = new Runnable() { - - @Override - public void run() { - mPendingAnimations.remove(v); - v.setScaleX(1); - v.setScaleY(1); - } - }; - if (oldItems[x][y] == null) { - v.setScaleX(0); - v.setScaleY(0); - v.animate().setDuration(SORT_ANIM_SHOW_DURATION) - .setStartDelay(SORT_ANIM_HIDE_DURATION + delay) - .scaleX(1).scaleY(1).setInterpolator(showInterpolator) - .withEndAction(clearPending); - mPendingAnimations.put(v, clearPending); - } else { - // Apply the old iconInfo so that there is no sudden change. - v.applyFromShortcutInfo(oldItems[x][y], mIconCache, false); - v.animate().setStartDelay(delay).setDuration(SORT_ANIM_HIDE_DURATION) - .scaleX(0).scaleY(0) - .setInterpolator(hideInterpolator) - .withEndAction(new Runnable() { - - @Override - public void run() { - // Apply the new iconInfo as part of the animation. - v.applyFromShortcutInfo(info, mIconCache, false); - v.animate().scaleX(1).scaleY(1) - .setDuration(SORT_ANIM_SHOW_DURATION).setStartDelay(0) - .setInterpolator(showInterpolator) - .withEndAction(clearPending); - } - }); - mPendingAnimations.put(v, new Runnable() { - - @Override - public void run() { - clearPending.run(); - v.applyFromShortcutInfo(info, mIconCache, false); - } - }); - } - delay += delayAmount; - delayAmount *= VIEW_REORDER_DELAY_FACTOR; - } - } - } - - setIsSorted(true, true); } /** @@ -295,7 +150,6 @@ public class FolderPagedView extends PagedView { * @return list of items that could not be bound, probably because we hit the max size limit. */ public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) { - mIsSorted = ALLOW_ITEM_SORTING && mFolder.mInfo.hasOption(FolderInfo.FLAG_ITEMS_SORTED); ArrayList<View> icons = new ArrayList<View>(); ArrayList<ShortcutInfo> extra = new ArrayList<ShortcutInfo>(); @@ -317,20 +171,6 @@ public class FolderPagedView extends PagedView { public int allocateRankForNewItem(ShortcutInfo info) { int rank = getItemCount(); ArrayList<View> views = new ArrayList<View>(mFolder.getItemsInReadingOrder()); - if (ALLOW_ITEM_SORTING && mIsSorted) { - View tmp = new View(getContext()); - tmp.setTag(info); - int index = Collections.binarySearch(views, tmp, new ViewComparator()); - if (index < 0) { - rank = -index - 1; - } else { - // Item with same name already exists. - // We will just insert it before that item. - rank = index; - } - - } - views.add(rank, null); arrangeChildren(views, views.size(), false); setCurrentPage(rank / mMaxItemsPerPage); @@ -363,7 +203,7 @@ public class FolderPagedView extends PagedView { } @SuppressLint("InflateParams") - private View createNewView(ShortcutInfo item) { + public View createNewView(ShortcutInfo item) { final BubbleTextView textView = (BubbleTextView) mInflater.inflate( R.layout.folder_application, null, false); textView.applyFromShortcutInfo(item, mIconCache, false); @@ -447,10 +287,6 @@ public class FolderPagedView extends PagedView { int position = 0; int newX, newY, rank; - boolean isSorted = mIsSorted; - - ViewComparator comparator = new ViewComparator(); - View lastView = null; rank = 0; for (int i = 0; i < itemCount; i++) { View v = list.size() > i ? list.get(i) : null; @@ -465,10 +301,6 @@ public class FolderPagedView extends PagedView { } if (v != null) { - if (lastView != null) { - isSorted &= comparator.compare(lastView, v) <= 0; - } - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); newX = position % mGridCountX; newY = position / mGridCountX; @@ -488,7 +320,6 @@ public class FolderPagedView extends PagedView { v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true); } - lastView = v; rank ++; position++; } @@ -506,23 +337,10 @@ public class FolderPagedView extends PagedView { setEnableOverscroll(getPageCount() > 1); // Update footer - if (ALLOW_ITEM_SORTING) { - setIsSorted(isSorted, saveChanges); - if (getPageCount() > 1) { - mPageIndicator.setVisibility(View.VISIBLE); - mSortButton.setVisibility(View.VISIBLE); - mFolder.mFolderName.setGravity(rtlLayout ? Gravity.RIGHT : Gravity.LEFT); - } else { - mPageIndicator.setVisibility(View.GONE); - mSortButton.setVisibility(View.GONE); - mFolder.mFolderName.setGravity(Gravity.CENTER_HORIZONTAL); - } - } else { - int indicatorVisibility = mPageIndicator.getVisibility(); - mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE); - if (indicatorVisibility != mPageIndicator.getVisibility()) { - mFolder.updateFooterHeight(); - } + int indicatorVisibility = mPageIndicator.getVisibility(); + mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE); + if (indicatorVisibility != mPageIndicator.getVisibility()) { + mFolder.updateFooterHeight(); } } @@ -559,7 +377,7 @@ public class FolderPagedView extends PagedView { public int findNearestArea(int pixelX, int pixelY) { int pageIndex = getNextPage(); CellLayout page = getPageAt(pageIndex); - page.findNearestArea(pixelX, pixelY, 1, 1, null, false, sTempPosArray); + page.findNearestArea(pixelX, pixelY, 1, 1, sTempPosArray); if (mFolder.isLayoutRtl()) { sTempPosArray[0] = page.getCountX() - sTempPosArray[0] - 1; } @@ -630,17 +448,6 @@ public class FolderPagedView extends PagedView { if (mFolder != null) { mFolder.updateTextViewFocus(); } - if (ALLOW_ITEM_SORTING && mSortOperationPending && getNextPage() == 0) { - post(new Runnable() { - - @Override - public void run() { - if (mSortOperationPending) { - doSort(); - } - } - }); - } } /** @@ -829,14 +636,4 @@ public class FolderPagedView extends PagedView { } } } - - private static class ViewComparator implements Comparator<View> { - private final Collator mCollator = Collator.getInstance(); - - @Override - public int compare(View lhs, View rhs) { - return mCollator.compare( ((ShortcutInfo) lhs.getTag()).title.toString(), - ((ShortcutInfo) rhs.getTag()).title.toString()); - } - } } diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java index e48640c93..f1ff48da3 100644 --- a/src/com/android/launcher3/InfoDropTarget.java +++ b/src/com/android/launcher3/InfoDropTarget.java @@ -21,7 +21,6 @@ import android.content.Context; import android.provider.Settings; import android.util.AttributeSet; -import com.android.launcher3.R; import com.android.launcher3.compat.UserHandleCompat; public class InfoDropTarget extends ButtonDropTarget { @@ -66,9 +65,13 @@ public class InfoDropTarget extends ButtonDropTarget { @Override protected boolean supportsDrop(DragSource source, Object info) { - return source.supportsAppInfoDropTarget() && - Settings.Global.getInt(getContext().getContentResolver(), - Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1; + return source.supportsAppInfoDropTarget() && supportsDrop(getContext(), info); + } + + public static boolean supportsDrop(Context context, Object info) { + return (Settings.Global.getInt(context.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1) && + (info instanceof AppInfo || info instanceof PendingAddItemInfo); } @Override diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 20844777f..8c920f0b8 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -100,8 +100,10 @@ import com.android.launcher3.compat.LauncherActivityInfoCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.PendingAddWidgetInfo; +import com.android.launcher3.widget.WidgetHostViewLoader; import com.android.launcher3.widget.WidgetsContainerView; import java.io.DataInputStream; @@ -302,7 +304,7 @@ public class Launcher extends Activity @Thunk static LocaleConfiguration sLocaleConfiguration = null; - private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>(); + private static LongArrayMap<FolderInfo> sFolders = new LongArrayMap<>(); private View.OnTouchListener mHapticFeedbackTouchListener; @@ -2047,12 +2049,6 @@ public class Launcher extends Activity TextKeyListener.getInstance().release(); - // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace - // to prevent leaking Launcher activities on orientation change. - if (mModel != null) { - mModel.unbindItemInfosAndClearQueuedBindRunnables(); - } - getContentResolver().unregisterContentObserver(mWidgetObserver); unregisterReceiver(mCloseSystemDialogsReceiver); @@ -3892,7 +3888,7 @@ public class Launcher extends Activity /** * Implementation of the method from LauncherModel.Callbacks. */ - public void bindFolders(final HashMap<Long, FolderInfo> folders) { + public void bindFolders(final LongArrayMap<FolderInfo> folders) { Runnable r = new Runnable() { public void run() { bindFolders(folders); @@ -3901,8 +3897,7 @@ public class Launcher extends Activity if (waitUntilResume(r)) { return; } - sFolders.clear(); - sFolders.putAll(folders); + sFolders = folders.clone(); } /** @@ -3950,7 +3945,7 @@ public class Launcher extends Activity pendingInfo.minSpanX = item.minSpanX; pendingInfo.minSpanY = item.minSpanY; Bundle options = null; - // AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo); + WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo); int newWidgetId = mAppWidgetHost.allocateAppWidgetId(); boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( diff --git a/src/com/android/launcher3/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/LauncherAccessibilityDelegate.java index 8ba02ea5f..a60e16024 100644 --- a/src/com/android/launcher3/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/LauncherAccessibilityDelegate.java @@ -1,16 +1,13 @@ package com.android.launcher3; import android.annotation.TargetApi; -import android.content.res.Resources; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.text.TextUtils; import android.util.SparseArray; import android.view.View; import android.view.View.AccessibilityDelegate; -import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; @@ -68,18 +65,21 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { if (!(host.getTag() instanceof ItemInfo)) return; ItemInfo item = (ItemInfo) host.getTag(); + if (DeleteDropTarget.supportsDrop(item)) { + info.addAction(mActions.get(REMOVE)); + } + if (UninstallDropTarget.supportsDrop(host.getContext(), item)) { + info.addAction(mActions.get(UNINSTALL)); + } + if (InfoDropTarget.supportsDrop(host.getContext(), item)) { + info.addAction(mActions.get(INFO)); + } + if ((item instanceof ShortcutInfo) || (item instanceof LauncherAppWidgetInfo) || (item instanceof FolderInfo)) { - // Workspace shortcut / widget - info.addAction(mActions.get(REMOVE)); info.addAction(mActions.get(MOVE)); - } else if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) { - // App or Widget from customization tray - if (item instanceof AppInfo) { - info.addAction(mActions.get(UNINSTALL)); - } - info.addAction(mActions.get(INFO)); + } if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) { info.addAction(mActions.get(ADD_TO_WORKSPACE)); } } @@ -94,10 +94,9 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { } public boolean performAction(View host, ItemInfo item, int action) { - Resources res = mLauncher.getResources(); if (action == REMOVE) { if (DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host)) { - announceConfirmation(R.string.item_removed_from_workspace); + announceConfirmation(R.string.item_removed); return true; } return false; @@ -105,9 +104,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { InfoDropTarget.startDetailsActivityForInfo(item, mLauncher); return true; } else if (action == UNINSTALL) { - AppInfo info = (AppInfo) item; - mLauncher.startApplicationUninstallActivity(info.componentName, info.flags, info.user); - return true; + return UninstallDropTarget.startUninstallActivity(mLauncher, item); } else if (action == MOVE) { beginAccessibleDrag(host, item); } else if (action == ADD_TO_WORKSPACE) { @@ -158,19 +155,31 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { return mDragInfo; } - public void handleAccessibleDrop(CellLayout targetContainer, Rect dropLocation, + /** + * @param clickedTarget the actual view that was clicked + * @param dropLocation relative to {@param clickedTarget}. If provided, its center is used + * as the actual drop location otherwise the views center is used. + */ + public void handleAccessibleDrop(View clickedTarget, Rect dropLocation, String confirmation) { if (!isInAccessibleDrag()) return; int[] loc = new int[2]; - loc[0] = dropLocation.centerX(); - loc[1] = dropLocation.centerY(); + if (dropLocation == null) { + loc[0] = clickedTarget.getWidth() / 2; + loc[1] = clickedTarget.getHeight() / 2; + } else { + loc[0] = dropLocation.centerX(); + loc[1] = dropLocation.centerY(); + } - mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(targetContainer, loc); + mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(clickedTarget, loc); mLauncher.getDragController().completeAccessibleDrag(loc); endAccessibleDrag(); - announceConfirmation(confirmation); + if (!TextUtils.isEmpty(confirmation)) { + announceConfirmation(confirmation); + } } public void beginAccessibleDrag(View item, ItemInfo info) { diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 5a65cab82..00afd98d0 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -28,7 +28,6 @@ import android.content.Context; import android.content.Intent; import android.content.Intent.ShortcutIconResource; import android.content.IntentFilter; -import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; @@ -61,6 +60,7 @@ import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.Thunk; @@ -109,11 +109,6 @@ public class LauncherModel extends BroadcastReceiver @Thunk LoaderTask mLoaderTask; @Thunk boolean mIsLoaderTaskRunning; - // Specific runnable types that are run on the main thread deferred handler, this allows us to - // clear all queued binding runnables when the Launcher activity is destroyed. - private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0; - private static final int MAIN_THREAD_BINDING_RUNNABLE = 1; - private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings"; @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); @@ -147,7 +142,7 @@ public class LauncherModel extends BroadcastReceiver // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by // LauncherModel to their ids - static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>(); + static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>(); // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts // created by LauncherModel that are directly on the home screen (however, no widgets or @@ -159,7 +154,7 @@ public class LauncherModel extends BroadcastReceiver new ArrayList<LauncherAppWidgetInfo>(); // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders() - static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>(); + static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>(); // sBgWorkspaceScreens is the ordered set of workspace screens. static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>(); @@ -188,7 +183,7 @@ public class LauncherModel extends BroadcastReceiver boolean forceAnimateIcons); public void bindScreens(ArrayList<Long> orderedScreenIds); public void bindAddScreens(ArrayList<Long> orderedScreenIds); - public void bindFolders(HashMap<Long,FolderInfo> folders); + public void bindFolders(LongArrayMap<FolderInfo> folders); public void finishBindingItems(); public void bindAppWidget(LauncherAppWidgetInfo info); public void bindAllApplications(ArrayList<AppInfo> apps); @@ -256,9 +251,6 @@ public class LauncherModel extends BroadcastReceiver /** Runs the specified runnable immediately if called from the main thread, otherwise it is * posted on the main thread handler. */ @Thunk void runOnMainThread(Runnable r) { - runOnMainThread(r, 0); - } - @Thunk void runOnMainThread(Runnable r, int type) { if (sWorkerThread.getThreadId() == Process.myTid()) { // If we are on the worker thread, post onto the main handler mHandler.post(r); @@ -295,7 +287,7 @@ public class LauncherModel extends BroadcastReceiver return; } - for (ItemInfo info : sBgItemsIdMap.values()) { + for (ItemInfo info : sBgItemsIdMap) { if (info instanceof ShortcutInfo) { ShortcutInfo si = (ShortcutInfo) info; ComponentName cn = si.getTargetComponent(); @@ -349,7 +341,7 @@ public class LauncherModel extends BroadcastReceiver final ArrayList<ShortcutInfo> updates = new ArrayList<>(); final UserHandleCompat user = UserHandleCompat.myUserHandle(); - for (ItemInfo info : sBgItemsIdMap.values()) { + for (ItemInfo info : sBgItemsIdMap) { if (info instanceof ShortcutInfo) { ShortcutInfo si = (ShortcutInfo) info; ComponentName cn = si.getTargetComponent(); @@ -442,7 +434,7 @@ public class LauncherModel extends BroadcastReceiver } } } - return CellLayout.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied); + return Utilities.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied); } /** @@ -679,7 +671,7 @@ public class LauncherModel extends BroadcastReceiver runOnWorkerThread(r); } - public void unbindItemInfosAndClearQueuedBindRunnables() { + private void unbindItemInfosAndClearQueuedBindRunnables() { if (sWorkerThread.getThreadId() == Process.myTid()) { throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " + "main thread"); @@ -689,8 +681,9 @@ public class LauncherModel extends BroadcastReceiver synchronized (mDeferredBindRunnables) { mDeferredBindRunnables.clear(); } - // Remove any queued bind runnables - mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE); + + // Remove any queued UI runnables + mHandler.cancelAll(); // Unbind all the workspace items unbindWorkspaceItemsOnMainThread(); } @@ -699,19 +692,15 @@ public class LauncherModel extends BroadcastReceiver void unbindWorkspaceItemsOnMainThread() { // Ensure that we don't use the same workspace items data structure on the main thread // by making a copy of workspace items first. - final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>(); - final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>(); + final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>(); synchronized (sBgLock) { - tmpWorkspaceItems.addAll(sBgWorkspaceItems); - tmpAppWidgets.addAll(sBgAppWidgets); + tmpItems.addAll(sBgWorkspaceItems); + tmpItems.addAll(sBgAppWidgets); } Runnable r = new Runnable() { @Override public void run() { - for (ItemInfo item : tmpWorkspaceItems) { - item.unbind(); - } - for (ItemInfo item : tmpAppWidgets) { + for (ItemInfo item : tmpItems) { item.unbind(); } } @@ -1008,7 +997,7 @@ public class LauncherModel extends BroadcastReceiver } synchronized (sBgLock) { - for (ItemInfo item : sBgItemsIdMap.values()) { + for (ItemInfo item : sBgItemsIdMap) { if (item instanceof ShortcutInfo) { ShortcutInfo info = (ShortcutInfo) item; if (intentWithPkg.equals(info.getIntent()) @@ -1024,7 +1013,7 @@ public class LauncherModel extends BroadcastReceiver /** * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList. */ - FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) { + FolderInfo getFolderById(Context context, LongArrayMap<FolderInfo> folderList, long id) { final ContentResolver cr = context.getContentResolver(); Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null, "_id=? and (itemType=? or itemType=?)", @@ -1144,7 +1133,7 @@ public class LauncherModel extends BroadcastReceiver return cn.getPackageName().equals(pn) && info.user.equals(user); } }; - return filterItemInfos(sBgItemsIdMap.values(), filter); + return filterItemInfos(sBgItemsIdMap, filter); } /** @@ -1184,7 +1173,7 @@ public class LauncherModel extends BroadcastReceiver switch (item.itemType) { case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: sBgFolders.remove(item.id); - for (ItemInfo info: sBgItemsIdMap.values()) { + for (ItemInfo info: sBgItemsIdMap) { if (info.container == item.id) { // We are deleting a folder which still contains items that // think they are contained by that folder. @@ -1297,6 +1286,9 @@ public class LauncherModel extends BroadcastReceiver */ public void initialize(Callbacks callbacks) { synchronized (mLock) { + // Disconnect any of the callbacks and drawables associated with ItemInfos on the + // workspace to prevent leaking Launcher activities on orientation change. + unbindItemInfosAndClearQueuedBindRunnables(); mCallbacks = new WeakReference<Callbacks>(callbacks); } } @@ -1486,7 +1478,7 @@ public class LauncherModel extends BroadcastReceiver mDeferredBindRunnables.clear(); } for (final Runnable r : deferredBindRunnables) { - mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE); + mHandler.post(r); } } } @@ -1758,7 +1750,7 @@ public class LauncherModel extends BroadcastReceiver } // check & update map of what's occupied; used to discard overlapping/invalid items - private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item) { + private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item) { LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); final int countX = (int) grid.numColumns; @@ -1906,7 +1898,7 @@ public class LauncherModel extends BroadcastReceiver // +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 // before any earlier duplicates) - final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>(); + final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>(); try { final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); @@ -2445,7 +2437,7 @@ public class LauncherModel extends BroadcastReceiver // Remove any empty screens ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens); - for (ItemInfo item: sBgItemsIdMap.values()) { + for (ItemInfo item: sBgItemsIdMap) { long screenId = item.screenId; if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && unusedScreens.contains(screenId)) { @@ -2470,14 +2462,13 @@ public class LauncherModel extends BroadcastReceiver for (int y = 0; y < countY; y++) { String line = ""; - Iterator<Long> iter = occupied.keySet().iterator(); - while (iter.hasNext()) { - long screenId = iter.next(); + for (int i = 0; i < nScreens; i++) { + long screenId = occupied.keyAt(i); if (screenId > 0) { line += " | "; } + ItemInfo[][] screen = occupied.valueAt(i); for (int x = 0; x < countX; x++) { - ItemInfo[][] screen = occupied.get(screenId); if (x < screen.length && y < screen[x].length) { line += (screen[x][y] != null) ? "#" : "."; } else { @@ -2568,14 +2559,17 @@ public class LauncherModel extends BroadcastReceiver /** Filters the set of folders which are on the specified screen. */ private void filterCurrentFolders(long currentScreenId, - HashMap<Long, ItemInfo> itemsIdMap, - HashMap<Long, FolderInfo> folders, - HashMap<Long, FolderInfo> currentScreenFolders, - HashMap<Long, FolderInfo> otherScreenFolders) { + LongArrayMap<ItemInfo> itemsIdMap, + LongArrayMap<FolderInfo> folders, + LongArrayMap<FolderInfo> currentScreenFolders, + LongArrayMap<FolderInfo> otherScreenFolders) { + + int total = folders.size(); + for (int i = 0; i < total; i++) { + long id = folders.keyAt(i); + FolderInfo folder = folders.valueAt(i); - for (long id : folders.keySet()) { ItemInfo info = itemsIdMap.get(id); - FolderInfo folder = folders.get(id); if (info == null || folder == null) continue; if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && info.screenId == currentScreenId) { @@ -2619,13 +2613,13 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); } private void bindWorkspaceItems(final Callbacks oldCallbacks, final ArrayList<ItemInfo> workspaceItems, final ArrayList<LauncherAppWidgetInfo> appWidgets, - final HashMap<Long, FolderInfo> folders, + final LongArrayMap<FolderInfo> folders, ArrayList<Runnable> deferredBindRunnables) { final boolean postOnMainThread = (deferredBindRunnables != null); @@ -2650,7 +2644,7 @@ public class LauncherModel extends BroadcastReceiver deferredBindRunnables.add(r); } } else { - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); } } @@ -2669,7 +2663,7 @@ public class LauncherModel extends BroadcastReceiver deferredBindRunnables.add(r); } } else { - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); } } @@ -2688,7 +2682,7 @@ public class LauncherModel extends BroadcastReceiver if (postOnMainThread) { deferredBindRunnables.add(r); } else { - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); } } } @@ -2713,15 +2707,18 @@ public class LauncherModel extends BroadcastReceiver ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>(); ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<LauncherAppWidgetInfo>(); - HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(); - HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>(); ArrayList<Long> orderedScreenIds = new ArrayList<Long>(); + + final LongArrayMap<FolderInfo> folders; + final LongArrayMap<ItemInfo> itemsIdMap; + synchronized (sBgLock) { workspaceItems.addAll(sBgWorkspaceItems); appWidgets.addAll(sBgAppWidgets); - folders.putAll(sBgFolders); - itemsIdMap.putAll(sBgItemsIdMap); orderedScreenIds.addAll(sBgWorkspaceScreens); + + folders = sBgFolders.clone(); + itemsIdMap = sBgItemsIdMap.clone(); } final boolean isLoadingSynchronously = @@ -2747,8 +2744,8 @@ public class LauncherModel extends BroadcastReceiver new ArrayList<LauncherAppWidgetInfo>(); ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<LauncherAppWidgetInfo>(); - HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>(); - HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>(); + LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>(); + LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>(); filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, otherWorkspaceItems); @@ -2768,7 +2765,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); bindWorkspaceScreens(oldCallbacks, orderedScreenIds); @@ -2784,7 +2781,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); } // Load all the remaining pages (if we are loading synchronously, we want to defer this @@ -2817,7 +2814,7 @@ public class LauncherModel extends BroadcastReceiver mDeferredBindRunnables.add(r); } } else { - runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); + runOnMainThread(r); } } @@ -2888,8 +2885,6 @@ public class LauncherModel extends BroadcastReceiver // Clear the list of apps mBgAllAppsList.clear(); - SharedPreferences prefs = mContext.getSharedPreferences( - LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE); for (UserHandleCompat user : profiles) { // Query for the set of apps final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; @@ -2913,7 +2908,7 @@ public class LauncherModel extends BroadcastReceiver if (!updatedPackages.isEmpty()) { final ArrayList<ShortcutInfo> updates = new ArrayList<ShortcutInfo>(); synchronized (sBgLock) { - for (ItemInfo info : sBgItemsIdMap.values()) { + for (ItemInfo info : sBgItemsIdMap) { if (info instanceof ShortcutInfo && user.equals(info.user) && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { ShortcutInfo si = (ShortcutInfo) info; @@ -3162,7 +3157,7 @@ public class LauncherModel extends BroadcastReceiver HashSet<String> packageSet = new HashSet<String>(Arrays.asList(packages)); synchronized (sBgLock) { - for (ItemInfo info : sBgItemsIdMap.values()) { + for (ItemInfo info : sBgItemsIdMap) { if (info instanceof ShortcutInfo && mUser.equals(info.user)) { ShortcutInfo si = (ShortcutInfo) info; boolean infoUpdated = false; @@ -3538,7 +3533,7 @@ public class LauncherModel extends BroadcastReceiver return info; } - static ArrayList<ItemInfo> filterItemInfos(Collection<ItemInfo> infos, + static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos, ItemInfoFilter f) { HashSet<ItemInfo> filtered = new HashSet<ItemInfo>(); for (ItemInfo i : infos) { @@ -3579,7 +3574,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - return filterItemInfos(sBgItemsIdMap.values(), filter); + return filterItemInfos(sBgItemsIdMap, filter); } /** @@ -3689,7 +3684,7 @@ public class LauncherModel extends BroadcastReceiver * Return an existing FolderInfo object if we have encountered this ID previously, * or make a new one. */ - @Thunk static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) { + @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) { // See if a placeholder was created for us already FolderInfo folderInfo = folders.get(id); if (folderInfo == null) { diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java index f91cfa07b..9e005f29a 100644 --- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java +++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java @@ -258,7 +258,7 @@ public class LauncherStateTransitionAnimation { // Setup the reveal view animation int width = revealView.getMeasuredWidth(); int height = revealView.getMeasuredHeight(); - float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4); + float revealRadius = (float) Math.hypot(width / 2, height / 2); revealView.setVisibility(View.VISIBLE); revealView.setAlpha(0f); revealView.setTranslationY(0f); @@ -563,7 +563,7 @@ public class LauncherStateTransitionAnimation { if (fromView.getVisibility() == View.VISIBLE) { int width = revealView.getMeasuredWidth(); int height = revealView.getMeasuredHeight(); - float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4); + float revealRadius = (float) Math.hypot(width / 2, height / 2); revealView.setVisibility(View.VISIBLE); revealView.setAlpha(1f); revealView.setTranslationY(0); diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java index af8bc7580..44a76b73b 100644 --- a/src/com/android/launcher3/SearchDropTargetBar.java +++ b/src/com/android/launcher3/SearchDropTargetBar.java @@ -180,6 +180,9 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D prepareStartAnimation(mDropTargetBar); mShowDropTargetBarAnim.start(); hideSearchBar(true); + if (mQSBSearchBar != null) { + mQSBSearchBar.setVisibility(View.GONE); + } } /** @@ -190,6 +193,9 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D prepareStartAnimation(mDropTargetBar); mShowDropTargetBarAnim.reverse(); showSearchBar(true); + if (mQSBSearchBar != null) { + mQSBSearchBar.setVisibility(View.VISIBLE); + } } /* @@ -228,4 +234,13 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D return null; } } + + public void enableAccessibleDrag(boolean enable) { + if (mQSBSearchBar != null) { + mQSBSearchBar.setVisibility(enable ? View.GONE : View.VISIBLE); + } + mInfoDropTarget.enableAccessibleDrag(enable); + mDeleteDropTarget.enableAccessibleDrag(enable); + mUninstallDropTarget.enableAccessibleDrag(enable); + } } diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java index 15b617683..56c8b39b6 100644 --- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java +++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java @@ -76,14 +76,6 @@ public class ShortcutAndWidgetContainer extends ViewGroup { return null; } - public void addView(View child, int index, LayoutParams params, boolean inLayout) { - if (!inLayout) { - addView(child, index, params); - } else { - addViewInLayout(child, index, params, false); - } - } - @Override protected void dispatchDraw(Canvas canvas) { @SuppressWarnings("all") // suppress dead code warning diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java index 4a7fffeb2..4c52d7e0f 100644 --- a/src/com/android/launcher3/UninstallDropTarget.java +++ b/src/com/android/launcher3/UninstallDropTarget.java @@ -33,9 +33,12 @@ public class UninstallDropTarget extends ButtonDropTarget { @Override protected boolean supportsDrop(DragSource source, Object info) { + return supportsDrop(getContext(), info); + } + + public static boolean supportsDrop(Context context, Object info) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - UserManager userManager = (UserManager) - getContext().getSystemService(Context.USER_SERVICE); + UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); Bundle restrictions = userManager.getUserRestrictions(); if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false) || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false)) { @@ -78,8 +81,7 @@ public class UninstallDropTarget extends ButtonDropTarget { void completeDrop(final DragObject d) { final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(d.dragInfo); final UserHandleCompat user = ((ItemInfo) d.dragInfo).user; - if (mLauncher.startApplicationUninstallActivity( - componentInfo.first, componentInfo.second, user)) { + if (startUninstallActivity(mLauncher, d.dragInfo)) { final Runnable checkIfUninstallWasSuccess = new Runnable() { @Override @@ -96,6 +98,13 @@ public class UninstallDropTarget extends ButtonDropTarget { } } + public static boolean startUninstallActivity(Launcher launcher, Object info) { + final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info); + final UserHandleCompat user = ((ItemInfo) info).user; + return launcher.startApplicationUninstallActivity( + componentInfo.first, componentInfo.second, user); + } + @Thunk void sendUninstallResult(DragSource target, boolean result) { if (target instanceof UninstallSource) { ((UninstallSource) target).onUninstallActivityReturned(result); diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 22677c8ea..2dbf078a4 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -583,4 +583,37 @@ public final class Utilities { return lhs.rank - rhs.rank; } }; + + /** + * 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; + } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index abb8489fd..2efd20739 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -63,6 +63,7 @@ import com.android.launcher3.Launcher.LauncherOverlay; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.UninstallDropTarget.UninstallSource; import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.WallpaperUtils; import com.android.launcher3.widget.PendingAddShortcutInfo; @@ -71,7 +72,6 @@ import com.android.launcher3.widget.PendingAddWidgetInfo; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.concurrent.atomic.AtomicInteger; /** @@ -120,7 +120,7 @@ public class Workspace extends SmoothPagedView final static long EXTRA_EMPTY_SCREEN_ID = -201; private final static long CUSTOM_CONTENT_SCREEN_ID = -301; - @Thunk HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>(); + @Thunk LongArrayMap<CellLayout> mWorkspaceScreens = new LongArrayMap<>(); @Thunk ArrayList<Long> mScreenOrder = new ArrayList<Long>(); @Thunk Runnable mRemoveEmptyScreenRunnable; @@ -834,12 +834,9 @@ public class Workspace extends SmoothPagedView } public long getIdForScreen(CellLayout layout) { - Iterator<Long> iter = mWorkspaceScreens.keySet().iterator(); - while (iter.hasNext()) { - long id = iter.next(); - if (mWorkspaceScreens.get(id) == layout) { - return id; - } + int index = mWorkspaceScreens.indexOfValue(layout); + if (index != -1) { + return mWorkspaceScreens.keyAt(index); } return -1; } @@ -874,8 +871,10 @@ public class Workspace extends SmoothPagedView int currentPage = getNextPage(); ArrayList<Long> removeScreens = new ArrayList<Long>(); - for (Long id: mWorkspaceScreens.keySet()) { - CellLayout cl = mWorkspaceScreens.get(id); + int total = mWorkspaceScreens.size(); + for (int i = 0; i < total; i++) { + long id = mWorkspaceScreens.keyAt(i); + CellLayout cl = mWorkspaceScreens.valueAt(i); if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) { removeScreens.add(id); } @@ -1615,6 +1614,7 @@ public class Workspace extends SmoothPagedView // Reset our click listener setOnClickListener(mLauncher); } + mLauncher.getSearchBar().enableAccessibleDrag(enable); mLauncher.getHotseat().getLayout().enableAccessibleDrag(enable); } diff --git a/src/com/android/launcher3/util/LongArrayMap.java b/src/com/android/launcher3/util/LongArrayMap.java new file mode 100644 index 000000000..e3c96cd42 --- /dev/null +++ b/src/com/android/launcher3/util/LongArrayMap.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.util; + +import android.util.LongSparseArray; + +import java.util.Iterator; + +/** + * Extension of {@link LongSparseArray} with some utility methods. + */ +public class LongArrayMap<E> extends LongSparseArray<E> implements Iterable<E> { + + public boolean containsKey(long key) { + return indexOfKey(key) >= 0; + } + + public boolean isEmpty() { + return size() <= 0; + } + + @Override + public LongArrayMap<E> clone() { + return (LongArrayMap<E>) super.clone(); + } + + @Override + public Iterator<E> iterator() { + return new ValueIterator(); + } + + private class ValueIterator implements Iterator<E> { + + private int mNextIndex = 0; + + @Override + public boolean hasNext() { + return mNextIndex < size(); + } + + @Override + public E next() { + return valueAt(mNextIndex ++); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 1ae75c3cc..0bc7333ec 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -24,8 +24,6 @@ import android.graphics.Bitmap; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; -import android.util.TypedValue; -import android.view.MotionEvent; import android.view.View; import android.view.View.OnLayoutChangeListener; import android.widget.ImageView; @@ -43,7 +41,7 @@ import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest; import com.android.launcher3.compat.AppWidgetManagerCompat; /** - * The linear layout used strictly for the widget tray. + * Represents the individual cell of the widget inside the widget tray. */ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { @@ -53,14 +51,12 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { private static final int FADE_IN_DURATION_MS = 70; private int mPresetPreviewSize; - private static WidgetCell sShortpressTarget = null; - + private ImageView mWidgetImage; + private TextView mWidgetName; + private TextView mWidgetDims; private final Rect mOriginalImagePadding = new Rect(); private String mDimensionsFormatString; - private CheckForShortPress mPendingCheckForShortPress = null; - private ShortPressListener mShortPressListener = null; - private boolean mShortPressTriggered = false; private boolean mIsAppWidget; private Object mInfo; @@ -92,57 +88,27 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { protected void onFinishInflate() { super.onFinishInflate(); - final ImageView image = (ImageView) findViewById(R.id.widget_preview); - mOriginalImagePadding.left = image.getPaddingLeft(); - mOriginalImagePadding.top = image.getPaddingTop(); - mOriginalImagePadding.right = image.getPaddingRight(); - mOriginalImagePadding.bottom = image.getPaddingBottom(); + mWidgetImage = (ImageView) findViewById(R.id.widget_preview); + mOriginalImagePadding.left = mWidgetImage.getPaddingLeft(); + mOriginalImagePadding.top = mWidgetImage.getPaddingTop(); + mOriginalImagePadding.right = mWidgetImage.getPaddingRight(); + mOriginalImagePadding.bottom = mWidgetImage.getPaddingBottom(); // Ensure we are using the right text size - LauncherAppState app = LauncherAppState.getInstance(); - DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); - TextView name = (TextView) findViewById(R.id.widget_name); - if (name != null) { - name.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx); - } - TextView dims = (TextView) findViewById(R.id.widget_dims); - if (dims != null) { - dims.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx); - } - } - - @Override - protected void onDetachedFromWindow() { - if (DEBUG) { - Log.d(TAG, String.format("[tag=%s] onDetachedFromWindow", getTagToString())); - } - super.onDetachedFromWindow(); - deletePreview(false); + DeviceProfile profile = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile(); + mWidgetName = ((TextView) findViewById(R.id.widget_name)); + mWidgetDims = ((TextView) findViewById(R.id.widget_dims)); } public void reset() { - ImageView image = (ImageView) findViewById(R.id.widget_preview); - final TextView name = (TextView) findViewById(R.id.widget_name); - final TextView dims = (TextView) findViewById(R.id.widget_dims); - image.setImageDrawable(null); - name.setText(null); - dims.setText(null); - } - - public void deletePreview(boolean recycleImage) { - if (recycleImage) { - final ImageView image = (ImageView) findViewById(R.id.widget_preview); - if (image != null) { - image.setImageDrawable(null); - } - } - - if (mActiveRequest != null) { - mActiveRequest.cancel(recycleImage); - mActiveRequest = null; - } + mWidgetImage.setImageDrawable(null); + mWidgetName.setText(null); + mWidgetDims.setText(null); } + /** + * Apply the widget provider info to the view. + */ public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info, int maxWidth, WidgetPreviewLoader loader) { LauncherAppState app = LauncherAppState.getInstance(); @@ -150,37 +116,41 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { mIsAppWidget = true; mInfo = info; - final ImageView image = (ImageView) findViewById(R.id.widget_preview); if (maxWidth > -1) { - image.setMaxWidth(maxWidth); - } - final TextView name = (TextView) findViewById(R.id.widget_name); - name.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info)); - final TextView dims = (TextView) findViewById(R.id.widget_dims); - if (dims != null) { - int hSpan = Math.min(info.spanX, (int) grid.numColumns); - int vSpan = Math.min(info.spanY, (int) grid.numRows); - dims.setText(String.format(mDimensionsFormatString, hSpan, vSpan)); + mWidgetImage.setMaxWidth(maxWidth); } + // TODO(hyunyoungs): setup a cache for these labels. + mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info)); + int hSpan = Math.min(info.spanX, (int) grid.numColumns); + int vSpan = Math.min(info.spanY, (int) grid.numRows); + mWidgetDims.setText(String.format(mDimensionsFormatString, hSpan, vSpan)); mWidgetPreviewLoader = loader; } + /** + * Apply the resolve info to the view. + */ public void applyFromResolveInfo( PackageManager pm, ResolveInfo info, WidgetPreviewLoader loader) { mIsAppWidget = false; mInfo = info; CharSequence label = info.loadLabel(pm); - final TextView name = (TextView) findViewById(R.id.widget_name); - name.setText(label); - final TextView dims = (TextView) findViewById(R.id.widget_dims); - if (dims != null) { - dims.setText(String.format(mDimensionsFormatString, 1, 1)); - } + mWidgetName.setText(label); + mWidgetDims.setText(String.format(mDimensionsFormatString, 1, 1)); mWidgetPreviewLoader = loader; } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + deletePreview(false); + + if (DEBUG) { + Log.d(TAG, String.format("[tag=%s] onDetachedFromWindow", getTagToString())); + } + } + public int[] getPreviewSize() { - final ImageView i = (ImageView) findViewById(R.id.widget_preview); int[] maxSize = new int[2]; maxSize[0] = mPresetPreviewSize; maxSize[1] = mPresetPreviewSize; @@ -189,110 +159,28 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { public void applyPreview(Bitmap bitmap) { FastBitmapDrawable preview = new FastBitmapDrawable(bitmap); - final WidgetImageView image = - (WidgetImageView) findViewById(R.id.widget_preview); if (DEBUG) { Log.d(TAG, String.format("[tag=%s] applyPreview preview: %s", getTagToString(), preview)); } if (preview != null) { - image.mAllowRequestLayout = false; - image.setImageDrawable(preview); + mWidgetImage.setImageDrawable(preview); if (mIsAppWidget) { // center horizontally int[] imageSize = getPreviewSize(); int centerAmount = (imageSize[0] - preview.getIntrinsicWidth()) / 2; - image.setPadding(mOriginalImagePadding.left + centerAmount, + mWidgetImage.setPadding(mOriginalImagePadding.left + centerAmount, mOriginalImagePadding.top, mOriginalImagePadding.right, mOriginalImagePadding.bottom); } - image.setAlpha(0f); - image.animate().alpha(1.0f).setDuration(FADE_IN_DURATION_MS); - image.mAllowRequestLayout = true; - image.requestLayout(); - } - } - - void setShortPressListener(ShortPressListener listener) { - mShortPressListener = listener; - } - - interface ShortPressListener { - void onShortPress(View v); - void cleanUpShortPress(View v); - } - - class CheckForShortPress implements Runnable { - public void run() { - if (sShortpressTarget != null) return; - if (mShortPressListener != null) { - mShortPressListener.onShortPress(WidgetCell.this); - sShortpressTarget = WidgetCell.this; - } - mShortPressTriggered = true; + mWidgetImage.setAlpha(0f); + mWidgetImage.animate().alpha(1.0f).setDuration(FADE_IN_DURATION_MS); + // TODO(hyunyoungs): figure out why this has to be called explicitly. + mWidgetImage.requestLayout(); } } - private void checkForShortPress() { - if (sShortpressTarget != null) return; - if (mPendingCheckForShortPress == null) { - mPendingCheckForShortPress = new CheckForShortPress(); - } - postDelayed(mPendingCheckForShortPress, 120); - } - - /** - * Remove the longpress detection timer. - */ - private void removeShortPressCallback() { - if (mPendingCheckForShortPress != null) { - removeCallbacks(mPendingCheckForShortPress); - } - } - - private void cleanUpShortPress() { - removeShortPressCallback(); - if (mShortPressTriggered) { - if (mShortPressListener != null) { - mShortPressListener.cleanUpShortPress(WidgetCell.this); - } - mShortPressTriggered = false; - } - } - - static void resetShortPressTarget() { - sShortpressTarget = null; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - super.onTouchEvent(event); - - switch (event.getAction()) { - case MotionEvent.ACTION_UP: - cleanUpShortPress(); - break; - case MotionEvent.ACTION_DOWN: - checkForShortPress(); - break; - case MotionEvent.ACTION_CANCEL: - cleanUpShortPress(); - break; - case MotionEvent.ACTION_MOVE: - break; - } - - // We eat up the touch events here, since the PagedView (which uses the same swiping - // touch code as Workspace previously) uses onInterceptTouchEvent() to determine when - // the user is scrolling between pages. This means that if the pages themselves don't - // handle touch events, it gets forwarded up to PagedView itself, and it's own - // onTouchEvent() handling will prevent further intercept touch events from being called - // (it's the same view in that case). This is not ideal, but to prevent more changes, - // we just always mark the touch event as handled. - return true; - } - public void ensurePreview() { if (mActiveRequest != null) { return; @@ -331,6 +219,16 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { return Math.min(size[0], info.spanX * cellWidth); } + + private void deletePreview(boolean recycleImage) { + mWidgetImage.setImageDrawable(null); + + if (mActiveRequest != null) { + mActiveRequest.cancel(recycleImage); + mActiveRequest = null; + } + } + /** * Helper method to get the string info of the tag. */ diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java new file mode 100644 index 000000000..d65455053 --- /dev/null +++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java @@ -0,0 +1,197 @@ +package com.android.launcher3.widget; + +import android.appwidget.AppWidgetHostView; +import android.appwidget.AppWidgetManager; +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.view.View; + +import com.android.launcher3.AppWidgetResizeFrame; +import com.android.launcher3.DragLayer; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.compat.AppWidgetManagerCompat; + +public class WidgetHostViewLoader { + + private static final boolean DEBUG = false; + private static final String TAG = "WidgetHostViewLoader"; + + /* constants used for widget loading state. */ + private static final int WIDGET_NO_CLEANUP_REQUIRED = -1; + private static final int WIDGET_PRELOAD_PENDING = 0; + private static final int WIDGET_BOUND = 1; + private static final int WIDGET_INFLATED = 2; + + int mState = WIDGET_NO_CLEANUP_REQUIRED; + + /* Runnables to handle inflation and binding. */ + private Runnable mInflateWidgetRunnable = null; + private Runnable mBindWidgetRunnable = null; + + /* Id of the widget being handled. */ + int mWidgetLoadingId = -1; + PendingAddWidgetInfo mCreateWidgetInfo = null; + + // TODO: technically, this class should not have to know the existence of the launcher. + private Launcher mLauncher; + private Handler mHandler; + + public WidgetHostViewLoader(Launcher launcher) { + mLauncher = launcher; + mHandler = new Handler(); + } + + /** + * Start loading the widget. + */ + public void load(View v) { + if (mCreateWidgetInfo != null) { + // Just in case the cleanup process wasn't properly executed. + finish(false); + } + boolean status = false; + if (v.getTag() instanceof PendingAddWidgetInfo) { + mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag()); + status = preloadWidget(v, mCreateWidgetInfo); + } + if (DEBUG) { + Log.d(TAG, String.format("load started on [state=%d, status=%s]", mState, status)); + } + } + + + /** + * Clean up according to what the last known state was. + * @param widgetIdUsed {@code true} if the widgetId was consumed which can happen only + * when view is fully inflated + */ + public void finish(boolean widgetIdUsed) { + if (DEBUG) { + Log.d(TAG, String.format("cancel on state [%d] widgetId=[%d]", + mState, mWidgetLoadingId)); + } + + // If the widget was not added, we may need to do further cleanup. + PendingAddWidgetInfo info = mCreateWidgetInfo; + mCreateWidgetInfo = null; + + if (mState == WIDGET_PRELOAD_PENDING) { + // We never did any preloading, so just remove pending callbacks to do so + mHandler.removeCallbacks(mBindWidgetRunnable); + mHandler.removeCallbacks(mInflateWidgetRunnable); + } else if (mState == WIDGET_BOUND) { + // Delete the widget id which was allocated + if (mWidgetLoadingId != -1 && !info.isCustomWidget()) { + mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId); + } + + // We never got around to inflating the widget, so remove the callback to do so. + mHandler.removeCallbacks(mInflateWidgetRunnable); + } else if (mState == WIDGET_INFLATED && !widgetIdUsed) { + // Delete the widget id which was allocated + if (mWidgetLoadingId != -1 && !info.isCustomWidget()) { + mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId); + } + + // The widget was inflated and added to the DragLayer -- remove it. + AppWidgetHostView widget = info.boundWidget; + mLauncher.getDragLayer().removeView(widget); + } + setState(WIDGET_NO_CLEANUP_REQUIRED); + mWidgetLoadingId = -1; + } + + private boolean preloadWidget(final View v, final PendingAddWidgetInfo info) { + final LauncherAppWidgetProviderInfo pInfo = info.info; + + final Bundle options = pInfo.isCustomWidget ? null : + getDefaultOptionsForWidget(mLauncher, info); + + // If there is a configuration activity, do not follow thru bound and inflate. + if (pInfo.configure != null) { + info.bindOptions = options; + return false; + } + setState(WIDGET_PRELOAD_PENDING); + mBindWidgetRunnable = new Runnable() { + @Override + public void run() { + if (pInfo.isCustomWidget) { + setState(WIDGET_BOUND); + return; + } + + mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId(); + if(AppWidgetManagerCompat.getInstance(mLauncher).bindAppWidgetIdIfAllowed( + mWidgetLoadingId, pInfo, options)) { + setState(WIDGET_BOUND); + } + } + }; + mHandler.post(mBindWidgetRunnable); + + mInflateWidgetRunnable = new Runnable() { + @Override + public void run() { + if (mState != WIDGET_BOUND) { + return; + } + AppWidgetHostView hostView = mLauncher.getAppWidgetHost().createView( + (Context) mLauncher, mWidgetLoadingId, pInfo); + info.boundWidget = hostView; + setState(WIDGET_INFLATED); + hostView.setVisibility(View.INVISIBLE); + int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info, false); + + // We want the first widget layout to be the correct size. This will be important + // for width size reporting to the AppWidgetManager. + DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0], + unScaledSize[1]); + lp.x = lp.y = 0; + lp.customPosition = true; + hostView.setLayoutParams(lp); + mLauncher.getDragLayer().addView(hostView); + v.setTag(info); + } + }; + mHandler.post(mInflateWidgetRunnable); + return true; + } + + public static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) { + Bundle options = null; + Rect rect = new Rect(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, rect); + Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher, + info.componentName, null); + + float density = launcher.getResources().getDisplayMetrics().density; + int xPaddingDips = (int) ((padding.left + padding.right) / density); + int yPaddingDips = (int) ((padding.top + padding.bottom) / density); + + options = new Bundle(); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, + rect.left - xPaddingDips); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, + rect.top - yPaddingDips); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, + rect.right - xPaddingDips); + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, + rect.bottom - yPaddingDips); + } + return options; + } + + private void setState(int state) { + if (DEBUG) { + Log.d(TAG, String.format(" state [%d -> %d]", mState, state)); + } + mState = state; + } +} diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 292a5de20..27a3ea13d 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -30,6 +30,7 @@ import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.Toast; import com.android.launcher3.CellLayout; import com.android.launcher3.DeleteDropTarget; @@ -54,15 +55,17 @@ import java.util.ArrayList; /** * The widgets list view container. */ -public class WidgetsContainerView extends FrameLayout implements Insettable, View.OnTouchListener, - View.OnLongClickListener, DragSource{ +public class WidgetsContainerView extends FrameLayout implements Insettable, + View.OnLongClickListener, View.OnClickListener, DragSource{ - private static final String TAG = "WidgetContainerView"; + private static final String TAG = "WidgetsContainerView"; private static final boolean DEBUG = false; /* {@link RecyclerView} will keep following # of views in cache, before recycling. */ private static final int WIDGET_CACHE_SIZE = 2; + private static final int SPRING_MODE_DELAY_MS = 150; + /* Global instances that are used inside this container. */ private Launcher mLauncher; private DragController mDragController; @@ -75,12 +78,13 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie private RecyclerView mView; private WidgetsListAdapter mAdapter; - /* Dragging related. */ - private boolean mDraggingWidget = false; // TODO(hyunyoungs): seems not needed? check! - private Point mLastTouchDownPos = new Point(); + /* Touch handling related member variables. */ + private Toast mWidgetInstructionToast; /* Rendering related. */ private WidgetPreviewLoader mWidgetPreviewLoader; + private WidgetHostViewLoader mWidgetHostViewLoader; + private Rect mPadding = new Rect(); public WidgetsContainerView(Context context) { @@ -95,8 +99,8 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie super(context, attrs, defStyleAttr); mLauncher = (Launcher) context; mDragController = mLauncher.getDragController(); - - mAdapter = new WidgetsListAdapter(context, this, mLauncher, this, mLauncher); + mWidgetHostViewLoader = new WidgetHostViewLoader(mLauncher); + mAdapter = new WidgetsListAdapter(context, this, this, mLauncher); mWidgets = new WidgetsModel(context, mAdapter); mAdapter.setWidgetsModel(mWidgets); mIconCache = (LauncherAppState.getInstance()).getIconCache(); @@ -147,11 +151,26 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie // @Override + public void onClick(View v) { + // When we have exited widget tray or are in transition, disregard clicks + if (!mLauncher.isWidgetsViewVisible() + || mLauncher.getWorkspace().isSwitchingState() + || !(v instanceof WidgetCell)) return; + + // Let the user know that they have to long press to add a widget + if (mWidgetInstructionToast != null) { + mWidgetInstructionToast.cancel(); + } + mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add, + Toast.LENGTH_SHORT); + mWidgetInstructionToast.show(); + } + + @Override public boolean onLongClick(View v) { if (DEBUG) { Log.d(TAG, String.format("onLonglick [v=%s]", v)); } - // Return early if this is not initiated from a touch if (!v.isInTouchMode()) return false; // When we have exited all apps or are in transition, disregard long clicks @@ -161,7 +180,11 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie Log.d(TAG, String.format("onLonglick dragging enabled?.", v)); if (!mLauncher.isDraggingEnabled()) return false; - return beginDragging(v); + boolean status = beginDragging(v); + if (status) { + mWidgetHostViewLoader.load(v); + } + return status; } private boolean beginDragging(View v) { @@ -174,7 +197,7 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie } // We delay entering spring-loaded mode slightly to make sure the UI - // thready is free of any work. + // thread is free of any work. postDelayed(new Runnable() { @Override public void run() { @@ -184,13 +207,12 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie mLauncher.enterSpringLoadedDragMode(); } } - }, 150); + }, SPRING_MODE_DELAY_MS); return true; } private boolean beginDraggingWidget(WidgetCell v) { - mDraggingWidget = true; // Get the widget preview as the drag representation ImageView image = (ImageView) v.findViewById(R.id.widget_preview); PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag(); @@ -198,7 +220,6 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and // we abort the drag. if (image.getDrawable() == null) { - mDraggingWidget = false; return false; } @@ -259,19 +280,6 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie return true; } - /* - * @see android.view.View.OnTouchListener#onTouch(android.view.View, android.view.MotionEvent) - */ - @Override - public boolean onTouch(View v, MotionEvent ev) { - Log.d(TAG, String.format("onTouch [MotionEvent=%s]", ev)); - if (ev.getAction() == MotionEvent.ACTION_DOWN || - ev.getAction() == MotionEvent.ACTION_MOVE) { - mLastTouchDownPos.set((int) ev.getX(), (int) ev.getY()); - } - return false; - } - // // Drag related handling methods that implement {@link DragSource} interface. // @@ -340,6 +348,10 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie } d.deferDragViewCleanupPostAnimation = false; } + //TODO(hyunyoungs): if drop fails, this call cleans up correctly. + // However, in rare corner case where drop succeeds but doesn't end up using the widget + // id created by the loader, this finish will leave dangling widget id. + mWidgetHostViewLoader.finish(success); } // @@ -368,5 +380,4 @@ public class WidgetsContainerView extends FrameLayout implements Insettable, Vie } return mWidgetPreviewLoader; } - }
\ No newline at end of file diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java index afeb2d385..f6ab21eb4 100644 --- a/src/com/android/launcher3/widget/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java @@ -56,20 +56,16 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { private WidgetsModel mWidgetsModel; private WidgetPreviewLoader mWidgetPreviewLoader; - private View.OnTouchListener mTouchListener; private View.OnClickListener mIconClickListener; private View.OnLongClickListener mIconLongClickListener; - public WidgetsListAdapter(Context context, - View.OnTouchListener touchListener, View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener, Launcher launcher) { mLayoutInflater = LayoutInflater.from(context); mContext = context; - mTouchListener = touchListener; mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; @@ -109,7 +105,6 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { // set up touch. widget.setOnClickListener(mIconClickListener); widget.setOnLongClickListener(mIconLongClickListener); - widget.setOnTouchListener(mTouchListener); row.addView(widget); } } else if (diff < 0) { |