Merge "Use AsyncTasks instead of creating new threads" into jb-ub-now-jolly-elf
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index d9ca157..688ff82 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -916,6 +916,13 @@
}
@Override
+ public float getIntrinsicIconScaleFactor() {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ return (float) grid.allAppsIconSizePx / grid.iconSizePx;
+ }
+
+ @Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
cancelAllTasks();
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
new file mode 100644
index 0000000..626ec42
--- /dev/null
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) 2008 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;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetrics;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+
+class DeviceProfileQuery {
+ float widthDps;
+ float heightDps;
+ float value;
+ PointF dimens;
+
+ DeviceProfileQuery(float w, float h, float v) {
+ widthDps = w;
+ heightDps = h;
+ value = v;
+ dimens = new PointF(w, h);
+ }
+}
+
+public class DeviceProfile {
+ public static interface DeviceProfileCallbacks {
+ public void onAvailableSizeChanged(DeviceProfile grid);
+ }
+
+ String name;
+ float minWidthDps;
+ float minHeightDps;
+ float numRows;
+ float numColumns;
+ float numHotseatIcons;
+ private float iconSize;
+ private float iconTextSize;
+ private int iconDrawablePaddingOriginalPx;
+ private float hotseatIconSize;
+
+ boolean isLandscape;
+ boolean isTablet;
+ boolean isLargeTablet;
+ boolean transposeLayoutWithOrientation;
+
+ int desiredWorkspaceLeftRightMarginPx;
+ int edgeMarginPx;
+ Rect defaultWidgetPadding;
+
+ int widthPx;
+ int heightPx;
+ int availableWidthPx;
+ int availableHeightPx;
+ int defaultPageSpacingPx;
+
+ int overviewModeMinIconZoneHeightPx;
+ int overviewModeMaxIconZoneHeightPx;
+ int overviewModeMaxBarWidthPx;
+ float overviewModeIconZoneRatio;
+ float overviewModeScaleFactor;
+
+ int iconSizePx;
+ int iconTextSizePx;
+ int iconDrawablePaddingPx;
+ int cellWidthPx;
+ int cellHeightPx;
+ int allAppsIconSizePx;
+ int allAppsIconTextSizePx;
+ int allAppsCellWidthPx;
+ int allAppsCellHeightPx;
+ int allAppsCellPaddingPx;
+ int folderBackgroundOffset;
+ int folderIconSizePx;
+ int folderCellWidthPx;
+ int folderCellHeightPx;
+ int hotseatCellWidthPx;
+ int hotseatCellHeightPx;
+ int hotseatIconSizePx;
+ int hotseatBarHeightPx;
+ int hotseatAllAppsRank;
+ int allAppsNumRows;
+ int allAppsNumCols;
+ int searchBarSpaceWidthPx;
+ int searchBarSpaceMaxWidthPx;
+ int searchBarSpaceHeightPx;
+ int searchBarHeightPx;
+ int pageIndicatorHeightPx;
+
+ private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
+
+ DeviceProfile(String n, float w, float h, float r, float c,
+ float is, float its, float hs, float his) {
+ // Ensure that we have an odd number of hotseat items (since we need to place all apps)
+ if (!AppsCustomizePagedView.DISABLE_ALL_APPS && hs % 2 == 0) {
+ throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
+ }
+
+ name = n;
+ minWidthDps = w;
+ minHeightDps = h;
+ numRows = r;
+ numColumns = c;
+ iconSize = is;
+ iconTextSize = its;
+ numHotseatIcons = hs;
+ hotseatIconSize = his;
+ }
+
+ DeviceProfile(Context context,
+ ArrayList<DeviceProfile> profiles,
+ float minWidth, float minHeight,
+ int wPx, int hPx,
+ int awPx, int ahPx,
+ Resources res) {
+ DisplayMetrics dm = res.getDisplayMetrics();
+ ArrayList<DeviceProfileQuery> points =
+ new ArrayList<DeviceProfileQuery>();
+ transposeLayoutWithOrientation =
+ res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
+ minWidthDps = minWidth;
+ minHeightDps = minHeight;
+
+ ComponentName cn = new ComponentName(context.getPackageName(),
+ this.getClass().getName());
+ defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
+ edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
+ desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
+ pageIndicatorHeightPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
+ defaultPageSpacingPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
+ allAppsCellPaddingPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
+ overviewModeMinIconZoneHeightPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
+ overviewModeMaxIconZoneHeightPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
+ overviewModeMaxBarWidthPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_max_width);
+ overviewModeIconZoneRatio =
+ res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
+ overviewModeScaleFactor =
+ res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
+
+ // Interpolate the rows
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
+ }
+ numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+ // Interpolate the columns
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
+ }
+ numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+ // Interpolate the hotseat length
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
+ }
+ numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+ hotseatAllAppsRank = (int) (numHotseatIcons / 2);
+
+ // Interpolate the icon size
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
+ }
+ iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+ // AllApps uses the original non-scaled icon size
+ allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
+
+ // Interpolate the icon text size
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
+ }
+ iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+ iconDrawablePaddingOriginalPx =
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
+ // AllApps uses the original non-scaled icon text size
+ allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
+
+ // Interpolate the hotseat icon size
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
+ }
+ // Hotseat
+ hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+
+ // Calculate the remaining vars
+ updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
+ updateAvailableDimensions(context);
+ }
+
+ void addCallback(DeviceProfileCallbacks cb) {
+ mCallbacks.add(cb);
+ cb.onAvailableSizeChanged(this);
+ }
+ void removeCallback(DeviceProfileCallbacks cb) {
+ mCallbacks.remove(cb);
+ }
+
+ private int getDeviceOrientation(Context context) {
+ WindowManager windowManager = (WindowManager)
+ context.getSystemService(Context.WINDOW_SERVICE);
+ Resources resources = context.getResources();
+ DisplayMetrics dm = resources.getDisplayMetrics();
+ Configuration config = resources.getConfiguration();
+ int rotation = windowManager.getDefaultDisplay().getRotation();
+
+ boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
+ (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
+ boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
+ (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
+ if (isLandscape || isRotatedPortrait) {
+ return CellLayout.LANDSCAPE;
+ } else {
+ return CellLayout.PORTRAIT;
+ }
+ }
+
+ private void updateAvailableDimensions(Context context) {
+ WindowManager windowManager = (WindowManager)
+ context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = windowManager.getDefaultDisplay();
+ Resources resources = context.getResources();
+ DisplayMetrics dm = resources.getDisplayMetrics();
+ Configuration config = resources.getConfiguration();
+
+ // There are three possible configurations that the dynamic grid accounts for, portrait,
+ // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
+ // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
+ // the height is the smallest height (either with the nav bar at the bottom or to the
+ // side) and otherwise, the height is simply the largest possible height for a portrait
+ // device.
+ Point size = new Point();
+ Point smallestSize = new Point();
+ Point largestSize = new Point();
+ display.getSize(size);
+ display.getCurrentSizeRange(smallestSize, largestSize);
+ availableWidthPx = size.x;
+ if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ availableHeightPx = smallestSize.y;
+ } else {
+ availableHeightPx = largestSize.y;
+ }
+
+ // Check to see if the icons fit in the new available height. If not, then we need to
+ // shrink the icon size.
+ Rect workspacePadding = getWorkspacePadding();
+ float scale = 1f;
+ int drawablePadding = iconDrawablePaddingOriginalPx;
+ updateIconSize(1f, drawablePadding, resources, dm);
+ float usedHeight = (cellHeightPx * numRows);
+ int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
+ if (usedHeight > maxHeight) {
+ scale = maxHeight / usedHeight;
+ drawablePadding = 0;
+ }
+ updateIconSize(scale, drawablePadding, resources, dm);
+
+ // Make the callbacks
+ for (DeviceProfileCallbacks cb : mCallbacks) {
+ cb.onAvailableSizeChanged(this);
+ }
+ }
+
+ private void updateIconSize(float scale, int drawablePadding, Resources resources,
+ DisplayMetrics dm) {
+ iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
+ iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
+ iconDrawablePaddingPx = drawablePadding;
+ hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
+
+ // Search Bar
+ searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
+ searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
+ searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
+ searchBarSpaceHeightPx = searchBarHeightPx + 2 * edgeMarginPx;
+
+ // Calculate the actual text height
+ Paint textPaint = new Paint();
+ textPaint.setTextSize(iconTextSizePx);
+ FontMetrics fm = textPaint.getFontMetrics();
+ cellWidthPx = iconSizePx;
+ cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
+
+ // Hotseat
+ hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
+ hotseatCellWidthPx = iconSizePx;
+ hotseatCellHeightPx = iconSizePx;
+
+ // Folder
+ folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
+ folderCellHeightPx = cellHeightPx + edgeMarginPx;
+ folderBackgroundOffset = -edgeMarginPx;
+ folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
+
+ // All Apps
+ Rect padding = getWorkspacePadding(isLandscape ?
+ CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+ int pageIndicatorOffset =
+ resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
+ allAppsCellWidthPx = allAppsIconSizePx;
+ allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
+ int maxLongEdgeCellCount =
+ resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
+ int maxShortEdgeCellCount =
+ resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
+ int minEdgeCellCount =
+ resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
+ int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
+ int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
+
+ allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
+ (allAppsCellHeightPx + allAppsCellPaddingPx);
+ allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
+ allAppsNumCols = (availableWidthPx) /
+ (allAppsCellWidthPx + allAppsCellPaddingPx);
+ allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
+ }
+
+ void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
+ int awPx, int ahPx) {
+ isLandscape = (resources.getConfiguration().orientation ==
+ Configuration.ORIENTATION_LANDSCAPE);
+ isTablet = resources.getBoolean(R.bool.is_tablet);
+ isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
+ widthPx = wPx;
+ heightPx = hPx;
+ availableWidthPx = awPx;
+ availableHeightPx = ahPx;
+
+ updateAvailableDimensions(context);
+ }
+
+ private 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));
+ }
+
+ private float weight(PointF a, PointF b,
+ float pow) {
+ float d = dist(a, b);
+ if (d == 0f) {
+ return Float.POSITIVE_INFINITY;
+ }
+ return (float) (1f / Math.pow(d, pow));
+ }
+
+ private float invDistWeightedInterpolate(float width, float height,
+ ArrayList<DeviceProfileQuery> points) {
+ float sum = 0;
+ float weights = 0;
+ float pow = 5;
+ float kNearestNeighbors = 3;
+ final PointF xy = new PointF(width, height);
+
+ ArrayList<DeviceProfileQuery> pointsByNearness = points;
+ Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
+ public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
+ return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
+ }
+ });
+
+ for (int i = 0; i < pointsByNearness.size(); ++i) {
+ DeviceProfileQuery p = pointsByNearness.get(i);
+ if (i < kNearestNeighbors) {
+ float w = weight(xy, p.dimens, pow);
+ if (w == Float.POSITIVE_INFINITY) {
+ return p.value;
+ }
+ weights += w;
+ }
+ }
+
+ for (int i = 0; i < pointsByNearness.size(); ++i) {
+ DeviceProfileQuery p = pointsByNearness.get(i);
+ if (i < kNearestNeighbors) {
+ float w = weight(xy, p.dimens, pow);
+ sum += w * p.value / weights;
+ }
+ }
+
+ return sum;
+ }
+
+ /** Returns the search bar bounds in the current orientation */
+ Rect getSearchBarBounds() {
+ return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+ }
+ /** Returns the search bar bounds in the specified orientation */
+ Rect getSearchBarBounds(int orientation) {
+ Rect bounds = new Rect();
+ if (orientation == CellLayout.LANDSCAPE &&
+ transposeLayoutWithOrientation) {
+ bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx, availableHeightPx - edgeMarginPx);
+ } else {
+ if (isTablet()) {
+ // Pad the left and right of the workspace to ensure consistent spacing
+ // between all icons
+ int width = (orientation == CellLayout.LANDSCAPE)
+ ? Math.max(widthPx, heightPx)
+ : Math.min(widthPx, heightPx);
+ // XXX: If the icon size changes across orientations, we will have to take
+ // that into account here too.
+ int gap = (int) ((width - 2 * edgeMarginPx -
+ (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
+ bounds.set(edgeMarginPx + gap, 0, availableWidthPx - (edgeMarginPx + gap),
+ searchBarSpaceHeightPx);
+ } else {
+ bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left, 0,
+ availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
+ defaultWidgetPadding.right), searchBarSpaceHeightPx);
+ }
+ }
+ return bounds;
+ }
+
+ /** Returns the workspace padding in the specified orientation */
+ Rect getWorkspacePadding() {
+ return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+ }
+ Rect getWorkspacePadding(int orientation) {
+ Rect searchBarBounds = getSearchBarBounds(orientation);
+ Rect padding = new Rect();
+ if (orientation == CellLayout.LANDSCAPE &&
+ transposeLayoutWithOrientation) {
+ // Pad the left and right of the workspace with search/hotseat bar sizes
+ padding.set(searchBarBounds.right, edgeMarginPx,
+ hotseatBarHeightPx, edgeMarginPx);
+ } else {
+ if (isTablet()) {
+ // Pad the left and right of the workspace to ensure consistent spacing
+ // between all icons
+ int width = (orientation == CellLayout.LANDSCAPE)
+ ? Math.max(widthPx, heightPx)
+ : Math.min(widthPx, heightPx);
+ // XXX: If the icon size changes across orientations, we will have to take
+ // that into account here too.
+ int gap = (int) ((width - 2 * edgeMarginPx -
+ (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
+ padding.set(edgeMarginPx + gap,
+ searchBarBounds.bottom,
+ edgeMarginPx + gap,
+ hotseatBarHeightPx + pageIndicatorHeightPx);
+ } else {
+ // Pad the top and bottom of the workspace with search/hotseat bar sizes
+ padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
+ searchBarBounds.bottom,
+ desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
+ hotseatBarHeightPx + pageIndicatorHeightPx);
+ }
+ }
+ return padding;
+ }
+
+ int getWorkspacePageSpacing(int orientation) {
+ if (orientation == CellLayout.LANDSCAPE &&
+ transposeLayoutWithOrientation) {
+ // In landscape mode the page spacing is set to the default.
+ return defaultPageSpacingPx;
+ } else {
+ // In portrait, we want the pages spaced such that there is no
+ // overhang of the previous / next page into the current page viewport.
+ // We assume symmetrical padding in portrait mode.
+ return 2 * getWorkspacePadding().left;
+ }
+ }
+
+ Rect getOverviewModeButtonBarRect() {
+ int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
+ zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
+ Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
+ return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
+ }
+
+ float getOverviewModeScale() {
+ Rect workspacePadding = getWorkspacePadding();
+ Rect overviewBar = getOverviewModeButtonBarRect();
+ int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
+ return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
+ }
+
+ // The rect returned will be extended to below the system ui that covers the workspace
+ Rect getHotseatRect() {
+ if (isVerticalBarLayout()) {
+ return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
+ Integer.MAX_VALUE, availableHeightPx);
+ } else {
+ return new Rect(0, availableHeightPx - hotseatBarHeightPx,
+ availableWidthPx, Integer.MAX_VALUE);
+ }
+ }
+
+ int calculateCellWidth(int width, int countX) {
+ return width / countX;
+ }
+ int calculateCellHeight(int height, int countY) {
+ return height / countY;
+ }
+
+ boolean isPhone() {
+ return !isTablet && !isLargeTablet;
+ }
+ boolean isTablet() {
+ return isTablet;
+ }
+ boolean isLargeTablet() {
+ return isLargeTablet;
+ }
+
+ boolean isVerticalBarLayout() {
+ return isLandscape && transposeLayoutWithOrientation;
+ }
+
+ boolean shouldFadeAdjacentWorkspaceScreens() {
+ return isVerticalBarLayout() || isLargeTablet();
+ }
+
+ public void layout(Launcher launcher) {
+ FrameLayout.LayoutParams lp;
+ Resources res = launcher.getResources();
+ boolean hasVerticalBarLayout = isVerticalBarLayout();
+
+ // Layout the search bar space
+ View searchBar = launcher.getSearchBar();
+ lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
+ if (hasVerticalBarLayout) {
+ // Vertical search bar
+ lp.gravity = Gravity.TOP | Gravity.LEFT;
+ lp.width = searchBarSpaceHeightPx;
+ lp.height = LayoutParams.MATCH_PARENT;
+ searchBar.setPadding(
+ 0, 2 * edgeMarginPx, 0,
+ 2 * edgeMarginPx);
+ } else {
+ // Horizontal search bar
+ lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+ lp.width = searchBarSpaceWidthPx;
+ lp.height = searchBarSpaceHeightPx;
+ searchBar.setPadding(
+ 2 * edgeMarginPx,
+ 2 * edgeMarginPx,
+ 2 * edgeMarginPx, 0);
+ }
+ searchBar.setLayoutParams(lp);
+
+ // Layout the search bar
+ View qsbBar = launcher.getQsbBar();
+ LayoutParams vglp = qsbBar.getLayoutParams();
+ vglp.width = LayoutParams.MATCH_PARENT;
+ vglp.height = LayoutParams.MATCH_PARENT;
+ qsbBar.setLayoutParams(vglp);
+
+ // Layout the voice proxy
+ View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
+ if (voiceButtonProxy != null) {
+ if (hasVerticalBarLayout) {
+ // TODO: MOVE THIS INTO SEARCH BAR MEASURE
+ } else {
+ lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
+ lp.gravity = Gravity.TOP | Gravity.END;
+ lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
+ 2 * iconSizePx;
+ lp.height = searchBarSpaceHeightPx;
+ }
+ }
+
+ // Layout the workspace
+ PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
+ lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
+ lp.gravity = Gravity.CENTER;
+ int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
+ Rect padding = getWorkspacePadding(orientation);
+ workspace.setLayoutParams(lp);
+ workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
+ workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
+
+ // Layout the hotseat
+ View hotseat = launcher.findViewById(R.id.hotseat);
+ lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
+ if (hasVerticalBarLayout) {
+ // Vertical hotseat
+ lp.gravity = Gravity.RIGHT;
+ lp.width = hotseatBarHeightPx;
+ lp.height = LayoutParams.MATCH_PARENT;
+ hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
+ } else if (isTablet()) {
+ // Pad the hotseat with the grid gap calculated above
+ int gridGap = (int) ((widthPx - 2 * edgeMarginPx -
+ (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
+ int gridWidth = (int) ((numColumns * cellWidthPx) +
+ ((numColumns - 1) * gridGap));
+ int hotseatGap = (int) Math.max(0,
+ (gridWidth - (numHotseatIcons * hotseatCellWidthPx))
+ / (numHotseatIcons - 1));
+ lp.gravity = Gravity.BOTTOM;
+ lp.width = LayoutParams.MATCH_PARENT;
+ lp.height = hotseatBarHeightPx;
+ hotseat.setPadding(2 * edgeMarginPx + gridGap + hotseatGap, 0,
+ 2 * edgeMarginPx + gridGap + hotseatGap,
+ 2 * edgeMarginPx);
+ } else {
+ // For phones, layout the hotseat without any bottom margin
+ // to ensure that we have space for the folders
+ lp.gravity = Gravity.BOTTOM;
+ lp.width = LayoutParams.MATCH_PARENT;
+ lp.height = hotseatBarHeightPx;
+ hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
+ 2 * edgeMarginPx, 0);
+ }
+ hotseat.setLayoutParams(lp);
+
+ // Layout the page indicators
+ View pageIndicator = launcher.findViewById(R.id.page_indicator);
+ if (pageIndicator != null) {
+ if (hasVerticalBarLayout) {
+ // Hide the page indicators when we have vertical search/hotseat
+ pageIndicator.setVisibility(View.GONE);
+ } else {
+ // Put the page indicators above the hotseat
+ lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
+ lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ lp.width = LayoutParams.WRAP_CONTENT;
+ lp.height = LayoutParams.WRAP_CONTENT;
+ lp.bottomMargin = hotseatBarHeightPx;
+ pageIndicator.setLayoutParams(lp);
+ }
+ }
+
+ // Layout AllApps
+ AppsCustomizeTabHost host = (AppsCustomizeTabHost)
+ launcher.findViewById(R.id.apps_customize_pane);
+ if (host != null) {
+ // Center the all apps page indicator
+ int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
+ (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
+ pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
+ if (pageIndicator != null) {
+ lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
+ lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ lp.width = LayoutParams.WRAP_CONTENT;
+ lp.height = pageIndicatorHeight;
+ pageIndicator.setLayoutParams(lp);
+ }
+
+ AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
+ host.findViewById(R.id.apps_customize_pane_content);
+ padding = new Rect();
+ if (pagedView != null) {
+ // Constrain the dimensions of all apps so that it does not span the full width
+ int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
+ (2 * (allAppsNumCols + 1));
+ int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
+ (2 * (allAppsNumRows + 1));
+ paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
+ paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
+ int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
+ int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
+ if (gridPaddingLR > (allAppsCellWidthPx / 4)) {
+ padding.left = padding.right = gridPaddingLR;
+ }
+ // The icons are centered, so we can't just offset by the page indicator height
+ // because the empty space will actually be pageIndicatorHeight + paddingTB
+ padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
+ pagedView.setAllAppsPadding(padding);
+ pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
+ }
+ }
+
+ // Layout the Overview Mode
+ View overviewMode = launcher.getOverviewPanel();
+ if (overviewMode != null) {
+ Rect r = getOverviewModeButtonBarRect();
+ lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
+ lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ lp.width = Math.min(availableWidthPx, overviewModeMaxBarWidthPx);
+ lp.height = r.height();
+ overviewMode.setLayoutParams(lp);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
index ab48233..1bfaa23 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -198,7 +198,7 @@
* @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
* Makes dragging feel more precise, e.g. you can clip out a transparent border
*/
- public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
+ public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
float initialDragViewScale) {
if (PROFILE_DRAWING_DURING_DRAG) {
@@ -245,6 +245,7 @@
mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
dragView.show(mMotionDownX, mMotionDownY);
handleMoveEvent(mMotionDownX, mMotionDownY);
+ return dragView;
}
/**
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index 159d7d9..dc0ba90 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -522,14 +522,18 @@
scale *= childScale;
int toX = coord[0];
int toY = coord[1];
+ float toScale = scale;
if (child instanceof TextView) {
TextView tv = (TextView) child;
+ // Account for the source scale of the icon (ie. from AllApps to Workspace, in which
+ // the workspace may have smaller icon bounds).
+ toScale = scale / dragView.getIntrinsicIconScaleFactor();
// The child may be scaled (always about the center of the view) so to account for it,
// we have to offset the position by the scaled size. Once we do that, we can center
// the drag view about the scaled child view.
- toY += Math.round(scale * tv.getPaddingTop());
- toY -= dragView.getMeasuredHeight() * (1 - scale) / 2;
+ toY += Math.round(toScale * tv.getPaddingTop());
+ toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
} else if (child instanceof FolderIcon) {
// Account for holographic blur padding on the drag view
@@ -555,7 +559,7 @@
}
}
};
- animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale,
+ animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
}
diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java
index 2ef99ae..cca9ab1 100644
--- a/src/com/android/launcher3/DragSource.java
+++ b/src/com/android/launcher3/DragSource.java
@@ -30,6 +30,11 @@
*/
boolean supportsFlingToDelete();
+ /*
+ * @return the scale of the icons over the workspace icon size
+ */
+ float getIntrinsicIconScaleFactor();
+
/**
* A callback specifically made back to the source after an item from this source has been flung
* to be deleted on a DropTarget. In such a situation, this method will be called after
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
index 686cf62..b66b55c 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/DragView.java
@@ -51,6 +51,9 @@
private float mOffsetX = 0.0f;
private float mOffsetY = 0.0f;
private float mInitialScale = 1f;
+ // The intrinsic icon scale factor is the scale factor for a drag icon over the workspace
+ // size. This is ignored for non-icons.
+ private float mIntrinsicIconScale = 1f;
/**
* Construct the drag view.
@@ -120,6 +123,15 @@
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
}
+ /** Sets the scale of the view over the normal workspace icon size. */
+ public void setIntrinsicIconScaleFactor(float scale) {
+ mIntrinsicIconScale = scale;
+ }
+
+ public float getIntrinsicIconScaleFactor() {
+ return mIntrinsicIconScale;
+ }
+
public float getOffsetY() {
return mOffsetY;
}
diff --git a/src/com/android/launcher3/DynamicGrid.java b/src/com/android/launcher3/DynamicGrid.java
index 5f8c011..ce78553 100644
--- a/src/com/android/launcher3/DynamicGrid.java
+++ b/src/com/android/launcher3/DynamicGrid.java
@@ -16,674 +16,14 @@
package com.android.launcher3;
-import android.appwidget.AppWidgetHostView;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Paint;
-import android.graphics.Paint.FontMetrics;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.TypedValue;
-import android.view.Display;
-import android.view.Gravity;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-class DeviceProfileQuery {
- float widthDps;
- float heightDps;
- float value;
- PointF dimens;
-
- DeviceProfileQuery(float w, float h, float v) {
- widthDps = w;
- heightDps = h;
- value = v;
- dimens = new PointF(w, h);
- }
-}
-
-class DeviceProfile {
- public static interface DeviceProfileCallbacks {
- public void onAvailableSizeChanged(DeviceProfile grid);
- }
-
- String name;
- float minWidthDps;
- float minHeightDps;
- float numRows;
- float numColumns;
- float numHotseatIcons;
- private float iconSize;
- private float iconTextSize;
- private int iconDrawablePaddingOriginalPx;
- private float hotseatIconSize;
-
- boolean isLandscape;
- boolean isTablet;
- boolean isLargeTablet;
- boolean transposeLayoutWithOrientation;
-
- int desiredWorkspaceLeftRightMarginPx;
- int edgeMarginPx;
- Rect defaultWidgetPadding;
-
- int widthPx;
- int heightPx;
- int availableWidthPx;
- int availableHeightPx;
- int defaultPageSpacingPx;
-
- int overviewModeMinIconZoneHeightPx;
- int overviewModeMaxIconZoneHeightPx;
- int overviewModeMaxBarWidthPx;
- float overviewModeIconZoneRatio;
- float overviewModeScaleFactor;
-
- int iconSizePx;
- int iconTextSizePx;
- int iconDrawablePaddingPx;
- int cellWidthPx;
- int cellHeightPx;
- int allAppsIconSizePx;
- int allAppsIconTextSizePx;
- int allAppsCellWidthPx;
- int allAppsCellHeightPx;
- int allAppsCellPaddingPx;
- int folderBackgroundOffset;
- int folderIconSizePx;
- int folderCellWidthPx;
- int folderCellHeightPx;
- int hotseatCellWidthPx;
- int hotseatCellHeightPx;
- int hotseatIconSizePx;
- int hotseatBarHeightPx;
- int hotseatAllAppsRank;
- int allAppsNumRows;
- int allAppsNumCols;
- int searchBarSpaceWidthPx;
- int searchBarSpaceMaxWidthPx;
- int searchBarSpaceHeightPx;
- int searchBarHeightPx;
- int pageIndicatorHeightPx;
-
- private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
-
- DeviceProfile(String n, float w, float h, float r, float c,
- float is, float its, float hs, float his) {
- // Ensure that we have an odd number of hotseat items (since we need to place all apps)
- if (!AppsCustomizePagedView.DISABLE_ALL_APPS && hs % 2 == 0) {
- throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
- }
-
- name = n;
- minWidthDps = w;
- minHeightDps = h;
- numRows = r;
- numColumns = c;
- iconSize = is;
- iconTextSize = its;
- numHotseatIcons = hs;
- hotseatIconSize = his;
- }
-
- DeviceProfile(Context context,
- ArrayList<DeviceProfile> profiles,
- float minWidth, float minHeight,
- int wPx, int hPx,
- int awPx, int ahPx,
- Resources res) {
- DisplayMetrics dm = res.getDisplayMetrics();
- ArrayList<DeviceProfileQuery> points =
- new ArrayList<DeviceProfileQuery>();
- transposeLayoutWithOrientation =
- res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
- minWidthDps = minWidth;
- minHeightDps = minHeight;
-
- ComponentName cn = new ComponentName(context.getPackageName(),
- this.getClass().getName());
- defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
- edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
- desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
- pageIndicatorHeightPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
- defaultPageSpacingPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
- allAppsCellPaddingPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
- overviewModeMinIconZoneHeightPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
- overviewModeMaxIconZoneHeightPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
- overviewModeMaxBarWidthPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_max_width);
- overviewModeIconZoneRatio =
- res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
- overviewModeScaleFactor =
- res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
-
- // Interpolate the rows
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
- }
- numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
- // Interpolate the columns
- points.clear();
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
- }
- numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
- // Interpolate the hotseat length
- points.clear();
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
- }
- numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
- hotseatAllAppsRank = (int) (numHotseatIcons / 2);
-
- // Interpolate the icon size
- points.clear();
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
- }
- iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
- // AllApps uses the original non-scaled icon size
- allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
-
- // Interpolate the icon text size
- points.clear();
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
- }
- iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
- iconDrawablePaddingOriginalPx =
- res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
- // AllApps uses the original non-scaled icon text size
- allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
-
- // Interpolate the hotseat icon size
- points.clear();
- for (DeviceProfile p : profiles) {
- points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
- }
- // Hotseat
- hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
-
- // Calculate the remaining vars
- updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
- updateAvailableDimensions(context);
- }
-
- void addCallback(DeviceProfileCallbacks cb) {
- mCallbacks.add(cb);
- cb.onAvailableSizeChanged(this);
- }
- void removeCallback(DeviceProfileCallbacks cb) {
- mCallbacks.remove(cb);
- }
-
- private int getDeviceOrientation(Context context) {
- WindowManager windowManager = (WindowManager)
- context.getSystemService(Context.WINDOW_SERVICE);
- Resources resources = context.getResources();
- DisplayMetrics dm = resources.getDisplayMetrics();
- Configuration config = resources.getConfiguration();
- int rotation = windowManager.getDefaultDisplay().getRotation();
-
- boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
- (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
- boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
- (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
- if (isLandscape || isRotatedPortrait) {
- return CellLayout.LANDSCAPE;
- } else {
- return CellLayout.PORTRAIT;
- }
- }
-
- private void updateAvailableDimensions(Context context) {
- WindowManager windowManager = (WindowManager)
- context.getSystemService(Context.WINDOW_SERVICE);
- Display display = windowManager.getDefaultDisplay();
- Resources resources = context.getResources();
- DisplayMetrics dm = resources.getDisplayMetrics();
- Configuration config = resources.getConfiguration();
-
- // There are three possible configurations that the dynamic grid accounts for, portrait,
- // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
- // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
- // the height is the smallest height (either with the nav bar at the bottom or to the
- // side) and otherwise, the height is simply the largest possible height for a portrait
- // device.
- Point size = new Point();
- Point smallestSize = new Point();
- Point largestSize = new Point();
- display.getSize(size);
- display.getCurrentSizeRange(smallestSize, largestSize);
- availableWidthPx = size.x;
- if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- availableHeightPx = smallestSize.y;
- } else {
- availableHeightPx = largestSize.y;
- }
-
- // Check to see if the icons fit in the new available height. If not, then we need to
- // shrink the icon size.
- Rect workspacePadding = getWorkspacePadding();
- float scale = 1f;
- int drawablePadding = iconDrawablePaddingOriginalPx;
- updateIconSize(1f, drawablePadding, resources, dm);
- float usedHeight = (cellHeightPx * numRows);
- int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
- if (usedHeight > maxHeight) {
- scale = maxHeight / usedHeight;
- drawablePadding = 0;
- }
- updateIconSize(scale, drawablePadding, resources, dm);
-
- // Make the callbacks
- for (DeviceProfileCallbacks cb : mCallbacks) {
- cb.onAvailableSizeChanged(this);
- }
- }
-
- private void updateIconSize(float scale, int drawablePadding, Resources resources,
- DisplayMetrics dm) {
- iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
- iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
- iconDrawablePaddingPx = drawablePadding;
- hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
-
- // Search Bar
- searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
- searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
- searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
- searchBarSpaceHeightPx = searchBarHeightPx + 2 * edgeMarginPx;
-
- // Calculate the actual text height
- Paint textPaint = new Paint();
- textPaint.setTextSize(iconTextSizePx);
- FontMetrics fm = textPaint.getFontMetrics();
- cellWidthPx = iconSizePx;
- cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
-
- // Hotseat
- hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
- hotseatCellWidthPx = iconSizePx;
- hotseatCellHeightPx = iconSizePx;
-
- // Folder
- folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
- folderCellHeightPx = cellHeightPx + edgeMarginPx;
- folderBackgroundOffset = -edgeMarginPx;
- folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
-
- // All Apps
- Rect padding = getWorkspacePadding(isLandscape ?
- CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
- int pageIndicatorOffset =
- resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
- allAppsCellWidthPx = allAppsIconSizePx;
- allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
- int maxLongEdgeCellCount =
- resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
- int maxShortEdgeCellCount =
- resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
- int minEdgeCellCount =
- resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
- int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
- int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
-
- allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
- (allAppsCellHeightPx + allAppsCellPaddingPx);
- allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
- allAppsNumCols = (availableWidthPx) /
- (allAppsCellWidthPx + allAppsCellPaddingPx);
- allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
- }
-
- void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
- int awPx, int ahPx) {
- isLandscape = (resources.getConfiguration().orientation ==
- Configuration.ORIENTATION_LANDSCAPE);
- isTablet = resources.getBoolean(R.bool.is_tablet);
- isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
- widthPx = wPx;
- heightPx = hPx;
- availableWidthPx = awPx;
- availableHeightPx = ahPx;
-
- updateAvailableDimensions(context);
- }
-
- private 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));
- }
-
- private float weight(PointF a, PointF b,
- float pow) {
- float d = dist(a, b);
- if (d == 0f) {
- return Float.POSITIVE_INFINITY;
- }
- return (float) (1f / Math.pow(d, pow));
- }
-
- private float invDistWeightedInterpolate(float width, float height,
- ArrayList<DeviceProfileQuery> points) {
- float sum = 0;
- float weights = 0;
- float pow = 5;
- float kNearestNeighbors = 3;
- final PointF xy = new PointF(width, height);
-
- ArrayList<DeviceProfileQuery> pointsByNearness = points;
- Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
- public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
- return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
- }
- });
-
- for (int i = 0; i < pointsByNearness.size(); ++i) {
- DeviceProfileQuery p = pointsByNearness.get(i);
- if (i < kNearestNeighbors) {
- float w = weight(xy, p.dimens, pow);
- if (w == Float.POSITIVE_INFINITY) {
- return p.value;
- }
- weights += w;
- }
- }
-
- for (int i = 0; i < pointsByNearness.size(); ++i) {
- DeviceProfileQuery p = pointsByNearness.get(i);
- if (i < kNearestNeighbors) {
- float w = weight(xy, p.dimens, pow);
- sum += w * p.value / weights;
- }
- }
-
- return sum;
- }
-
- Rect getWorkspacePadding() {
- return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
- }
-
- Rect getWorkspacePadding(int orientation) {
- Rect padding = new Rect();
- if (orientation == CellLayout.LANDSCAPE &&
- transposeLayoutWithOrientation) {
- // Pad the left and right of the workspace with search/hotseat bar sizes
- padding.set(searchBarSpaceHeightPx, edgeMarginPx,
- hotseatBarHeightPx, edgeMarginPx);
- } else {
- if (isTablet()) {
- // Pad the left and right of the workspace to ensure consistent spacing
- // between all icons
- int width = (orientation == CellLayout.LANDSCAPE)
- ? Math.max(widthPx, heightPx)
- : Math.min(widthPx, heightPx);
- // XXX: If the icon size changes across orientations, we will have to take
- // that into account here too.
- int gap = (int) ((width - 2 * edgeMarginPx -
- (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
- padding.set(edgeMarginPx + gap,
- searchBarSpaceHeightPx,
- edgeMarginPx + gap,
- hotseatBarHeightPx + pageIndicatorHeightPx);
- } else {
- // Pad the top and bottom of the workspace with search/hotseat bar sizes
- padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
- searchBarSpaceHeightPx,
- desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
- hotseatBarHeightPx + pageIndicatorHeightPx);
- }
- }
- return padding;
- }
-
- int getWorkspacePageSpacing(int orientation) {
- if (orientation == CellLayout.LANDSCAPE &&
- transposeLayoutWithOrientation) {
- // In landscape mode the page spacing is set to the default.
- return defaultPageSpacingPx;
- } else {
- // In portrait, we want the pages spaced such that there is no
- // overhang of the previous / next page into the current page viewport.
- // We assume symmetrical padding in portrait mode.
- return 2 * getWorkspacePadding().left;
- }
- }
-
- Rect getOverviewModeButtonBarRect() {
- int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
- zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
- Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
- return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
- }
-
- float getOverviewModeScale() {
- Rect workspacePadding = getWorkspacePadding();
- Rect overviewBar = getOverviewModeButtonBarRect();
- int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
- return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
- }
-
- // The rect returned will be extended to below the system ui that covers the workspace
- Rect getHotseatRect() {
- if (isVerticalBarLayout()) {
- return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
- Integer.MAX_VALUE, availableHeightPx);
- } else {
- return new Rect(0, availableHeightPx - hotseatBarHeightPx,
- availableWidthPx, Integer.MAX_VALUE);
- }
- }
-
- int calculateCellWidth(int width, int countX) {
- return width / countX;
- }
- int calculateCellHeight(int height, int countY) {
- return height / countY;
- }
-
- boolean isPhone() {
- return !isTablet && !isLargeTablet;
- }
- boolean isTablet() {
- return isTablet;
- }
- boolean isLargeTablet() {
- return isLargeTablet;
- }
-
- boolean isVerticalBarLayout() {
- return isLandscape && transposeLayoutWithOrientation;
- }
-
- boolean shouldFadeAdjacentWorkspaceScreens() {
- return isVerticalBarLayout() || isLargeTablet();
- }
-
- public void layout(Launcher launcher) {
- FrameLayout.LayoutParams lp;
- Resources res = launcher.getResources();
- boolean hasVerticalBarLayout = isVerticalBarLayout();
-
- // Layout the search bar space
- View searchBar = launcher.getSearchBar();
- lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
- if (hasVerticalBarLayout) {
- // Vertical search bar
- lp.gravity = Gravity.TOP | Gravity.LEFT;
- lp.width = searchBarSpaceHeightPx;
- lp.height = LayoutParams.MATCH_PARENT;
- searchBar.setPadding(
- 0, 2 * edgeMarginPx, 0,
- 2 * edgeMarginPx);
- } else {
- // Horizontal search bar
- lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
- lp.width = searchBarSpaceWidthPx;
- lp.height = searchBarSpaceHeightPx;
- searchBar.setPadding(
- 2 * edgeMarginPx,
- 2 * edgeMarginPx,
- 2 * edgeMarginPx, 0);
- }
- searchBar.setLayoutParams(lp);
-
- // Layout the search bar
- View qsbBar = launcher.getQsbBar();
- LayoutParams vglp = qsbBar.getLayoutParams();
- vglp.width = LayoutParams.MATCH_PARENT;
- vglp.height = LayoutParams.MATCH_PARENT;
- qsbBar.setLayoutParams(vglp);
-
- // Layout the voice proxy
- View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
- if (voiceButtonProxy != null) {
- if (hasVerticalBarLayout) {
- // TODO: MOVE THIS INTO SEARCH BAR MEASURE
- } else {
- lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
- lp.gravity = Gravity.TOP | Gravity.END;
- lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
- 2 * iconSizePx;
- lp.height = searchBarSpaceHeightPx;
- }
- }
-
- // Layout the workspace
- PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
- lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
- lp.gravity = Gravity.CENTER;
- int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
- Rect padding = getWorkspacePadding(orientation);
- workspace.setLayoutParams(lp);
- workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
- workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
-
- // Layout the hotseat
- View hotseat = launcher.findViewById(R.id.hotseat);
- lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
- if (hasVerticalBarLayout) {
- // Vertical hotseat
- lp.gravity = Gravity.RIGHT;
- lp.width = hotseatBarHeightPx;
- lp.height = LayoutParams.MATCH_PARENT;
- hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
- } else if (isTablet()) {
- // Pad the hotseat with the grid gap calculated above
- int gridGap = (int) ((widthPx - 2 * edgeMarginPx -
- (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
- int gridWidth = (int) ((numColumns * cellWidthPx) +
- ((numColumns - 1) * gridGap));
- int hotseatGap = (int) Math.max(0,
- (gridWidth - (numHotseatIcons * hotseatCellWidthPx))
- / (numHotseatIcons - 1));
- lp.gravity = Gravity.BOTTOM;
- lp.width = LayoutParams.MATCH_PARENT;
- lp.height = hotseatBarHeightPx;
- hotseat.setPadding(2 * edgeMarginPx + gridGap + hotseatGap, 0,
- 2 * edgeMarginPx + gridGap + hotseatGap,
- 2 * edgeMarginPx);
- } else {
- // For phones, layout the hotseat without any bottom margin
- // to ensure that we have space for the folders
- lp.gravity = Gravity.BOTTOM;
- lp.width = LayoutParams.MATCH_PARENT;
- lp.height = hotseatBarHeightPx;
- hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
- 2 * edgeMarginPx, 0);
- }
- hotseat.setLayoutParams(lp);
-
- // Layout the page indicators
- View pageIndicator = launcher.findViewById(R.id.page_indicator);
- if (pageIndicator != null) {
- if (hasVerticalBarLayout) {
- // Hide the page indicators when we have vertical search/hotseat
- pageIndicator.setVisibility(View.GONE);
- } else {
- // Put the page indicators above the hotseat
- lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
- lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- lp.width = LayoutParams.WRAP_CONTENT;
- lp.height = LayoutParams.WRAP_CONTENT;
- lp.bottomMargin = hotseatBarHeightPx;
- pageIndicator.setLayoutParams(lp);
- }
- }
-
- // Layout AllApps
- AppsCustomizeTabHost host = (AppsCustomizeTabHost)
- launcher.findViewById(R.id.apps_customize_pane);
- if (host != null) {
- // Center the all apps page indicator
- int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
- (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
- pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
- if (pageIndicator != null) {
- lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
- lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- lp.width = LayoutParams.WRAP_CONTENT;
- lp.height = pageIndicatorHeight;
- pageIndicator.setLayoutParams(lp);
- }
-
- AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
- host.findViewById(R.id.apps_customize_pane_content);
- padding = new Rect();
- if (pagedView != null) {
- // Constrain the dimensions of all apps so that it does not span the full width
- int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
- (2 * (allAppsNumCols + 1));
- int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
- (2 * (allAppsNumRows + 1));
- paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
- paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
- int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
- int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
- if (gridPaddingLR > (allAppsCellWidthPx / 4)) {
- padding.left = padding.right = gridPaddingLR;
- }
- // The icons are centered, so we can't just offset by the page indicator height
- // because the empty space will actually be pageIndicatorHeight + paddingTB
- padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
- pagedView.setAllAppsPadding(padding);
- pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
- }
- }
-
- // Layout the Overview Mode
- View overviewMode = launcher.getOverviewPanel();
- if (overviewMode != null) {
- Rect r = getOverviewModeButtonBarRect();
- lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
- lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- lp.width = Math.min(availableWidthPx, overviewModeMaxBarWidthPx);
- lp.height = r.height();
- overviewMode.setLayoutParams(lp);
- }
- }
-}
-
public class DynamicGrid {
@SuppressWarnings("unused")
private static final String TAG = "DynamicGrid";
@@ -734,10 +74,10 @@
// The tablet profile is odd in that the landscape orientation
// also includes the nav bar on the side
deviceProfiles.add(new DeviceProfile("Nexus 7",
- 575, 904, 6, 6, 72, 14.4f, 7, 60));
+ 575, 904, 5, 5, 72, 14.4f, 7, 60));
// Larger tablet profiles always have system bars on the top & bottom
deviceProfiles.add(new DeviceProfile("Nexus 10",
- 727, 1207, 5, 8, 80, 14.4f, 9, 64));
+ 727, 1207, 5, 5, 80, 14.4f, 7, 64));
/*
deviceProfiles.add(new DeviceProfile("Nexus 7",
600, 960, 5, 5, 72, 14.4f, 5, 60));
@@ -755,7 +95,7 @@
resources);
}
- DeviceProfile getDeviceProfile() {
+ public DeviceProfile getDeviceProfile() {
return mProfile;
}
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index bce6707..85e9020 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -27,19 +27,12 @@
class FastBitmapDrawable extends Drawable {
private Bitmap mBitmap;
private int mAlpha;
- private int mWidth;
- private int mHeight;
private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
FastBitmapDrawable(Bitmap b) {
- mAlpha = 255;
+ mAlpha = 255;
mBitmap = b;
- if (b != null) {
- mWidth = mBitmap.getWidth();
- mHeight = mBitmap.getHeight();
- } else {
- mWidth = mHeight = 0;
- }
+ setBounds(0, 0, b.getWidth(), b.getHeight());
}
@Override
@@ -76,32 +69,22 @@
@Override
public int getIntrinsicWidth() {
- return mWidth;
+ return getBounds().width();
}
@Override
public int getIntrinsicHeight() {
- return mHeight;
+ return getBounds().height();
}
@Override
public int getMinimumWidth() {
- return mWidth;
+ return getBounds().width();
}
@Override
public int getMinimumHeight() {
- return mHeight;
- }
-
- public void setBitmap(Bitmap b) {
- mBitmap = b;
- if (b != null) {
- mWidth = mBitmap.getWidth();
- mHeight = mBitmap.getHeight();
- } else {
- mWidth = mHeight = 0;
- }
+ return getBounds().height();
}
public Bitmap getBitmap() {
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 0514615..1d234ff 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -792,6 +792,11 @@
}
@Override
+ public float getIntrinsicIconScaleFactor() {
+ return 1f;
+ }
+
+ @Override
public boolean supportsFlingToDelete() {
return true;
}
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 835c472..fb75161 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -294,7 +294,7 @@
// Add the new apps to the model and bind them
if (!addShortcuts.isEmpty()) {
LauncherAppState app = LauncherAppState.getInstance();
- app.getModel().addAndBindAddedApps(context, addShortcuts, null);
+ app.getModel().addAndBindAddedApps(context, addShortcuts, new ArrayList<AppInfo>());
}
}
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b76379a..4d802bd 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -45,7 +45,6 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -113,6 +112,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Default launcher application.
@@ -181,8 +181,10 @@
private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
// Type: parcelable
private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
- // Type: parcelable
+ // Type: parcelable
private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
+ // Type: int[]
+ private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME =
@@ -207,6 +209,9 @@
private static final Object sLock = new Object();
private static int sScreen = DEFAULT_SCREEN;
+ private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
+ private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
+
// How long to wait before the new-shortcut animation automatically pans the workspace
private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
@@ -454,7 +459,7 @@
} else {
// We only load the page synchronously if the user rotates (or triggers a
// configuration change) while launcher is in the foreground
- mModel.startLoader(true, mWorkspace.getCurrentPage());
+ mModel.startLoader(true, mWorkspace.getRestorePage());
}
}
@@ -671,6 +676,34 @@
}
/**
+ * Copied from View -- the View version of the method isn't called
+ * anywhere else in our process and only exists for API level 17+,
+ * so it's ok to keep our own version with no API requirement.
+ */
+ public static int generateViewId() {
+ for (;;) {
+ final int result = sNextGeneratedId.get();
+ // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
+ int newValue = result + 1;
+ if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
+ if (sNextGeneratedId.compareAndSet(result, newValue)) {
+ return result;
+ }
+ }
+ }
+
+ public int getViewIdForItem(ItemInfo info) {
+ // This cast is safe given the > 2B range for int.
+ int itemId = (int) info.id;
+ if (mItemIdToViewId.containsKey(itemId)) {
+ return mItemIdToViewId.get(itemId);
+ }
+ int viewId = generateViewId();
+ mItemIdToViewId.put(itemId, viewId);
+ return viewId;
+ }
+
+ /**
* Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
* a configuration step, this allows the proper animations to run after other transitions.
*/
@@ -1108,6 +1141,7 @@
*
* @param savedState The previous state.
*/
+ @SuppressWarnings("unchecked")
private void restoreState(Bundle savedState) {
if (savedState == null) {
return;
@@ -1160,6 +1194,8 @@
int currentIndex = savedState.getInt("apps_customize_currentIndex");
mAppsCustomizeContent.restorePageForIndex(currentIndex);
}
+ mItemIdToViewId = (HashMap<Integer, Integer>)
+ savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
}
/**
@@ -1705,7 +1741,7 @@
// In all these cases, only animate if we're already on home
mWorkspace.exitWidgetResizeMode();
if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
- openFolder == null) {
+ openFolder == null && shouldMoveToDefaultScreenOnHomeIntent()) {
mWorkspace.moveToDefaultScreen(true);
}
@@ -1731,6 +1767,8 @@
if (mAppsCustomizeTabHost != null) {
mAppsCustomizeTabHost.reset();
}
+
+ onHomeIntent();
}
if (DEBUG_RESUME_TIME) {
@@ -1738,6 +1776,21 @@
}
}
+ /**
+ * Override point for subclasses to prevent movement to the default screen when the home
+ * button is pressed. Used (for example) in GEL, to prevent movement during a search.
+ */
+ protected boolean shouldMoveToDefaultScreenOnHomeIntent() {
+ return true;
+ }
+
+ /**
+ * Override point for subclasses to provide custom behaviour for when a home intent is fired.
+ */
+ protected void onHomeIntent() {
+ // Do nothing
+ }
+
@Override
public void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
@@ -1749,7 +1802,8 @@
@Override
protected void onSaveInstanceState(Bundle outState) {
if (mWorkspace.getChildCount() > 0) {
- outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getRestorePage());
+ outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
+ mWorkspace.getCurrentPageOffsetFromCustomContent());
}
super.onSaveInstanceState(outState);
@@ -1785,6 +1839,7 @@
int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
outState.putInt("apps_customize_currentIndex", currentIndex);
}
+ outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
}
@Override
@@ -1823,7 +1878,7 @@
mDragLayer.clearAllResizeFrames();
((ViewGroup) mWorkspace.getParent()).removeAllViews();
- mWorkspace.removeAllViews();
+ mWorkspace.removeAllWorkspaceScreens();
mWorkspace = null;
mDragController = null;
@@ -2609,7 +2664,7 @@
}
public void closeFolder() {
- Folder folder = mWorkspace.getOpenFolder();
+ Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
if (folder != null) {
if (folder.isEditingName()) {
folder.dismissEditingName();
@@ -3658,6 +3713,10 @@
@Override
public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
+ Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
+ TextUtils.join(", ", orderedScreenIds), true);
int count = orderedScreenIds.size();
for (int i = 0; i < count; i++) {
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
@@ -3965,6 +4024,11 @@
getDeviceProfile().isVerticalBarLayout();
}
+ protected Rect getSearchBarBounds() {
+ return LauncherAppState.getInstance().getDynamicGrid().
+ getDeviceProfile().getSearchBarBounds();
+ }
+
@Override
public void bindSearchablesChanged() {
boolean searchVisible = updateGlobalSearchIcon();
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index fe2b43f..9a47eaa 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -191,7 +191,7 @@
availableWidth, availableHeight);
return grid;
}
- DynamicGrid getDynamicGrid() {
+ public DynamicGrid getDynamicGrid() {
return mDynamicGrid;
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index dff1814..4d9dff9 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -40,6 +40,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.BaseColumns;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import com.android.launcher3.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
@@ -282,7 +283,10 @@
addAndBindAddedApps(context, workspaceApps, cb, allAppsApps);
}
public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps,
- final Callbacks callbacks, final ArrayList<AppInfo> allAppsApps) {
+ final Callbacks callbacks, final ArrayList<AppInfo> allAppsApps) {
+ if (workspaceApps == null || allAppsApps == null) {
+ throw new RuntimeException("workspaceApps and allAppsApps must not be null");
+ }
if (workspaceApps.isEmpty() && allAppsApps.isEmpty()) {
return;
}
@@ -955,6 +959,10 @@
* a list of screen ids in the order that they should appear.
*/
void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - updateWorkspaceScreenOrder()", true);
+ Launcher.addDumpLog(TAG, "11683562 - screens: " + TextUtils.join(", ", screens), true);
+
final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
final ContentResolver cr = context.getContentResolver();
final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -1235,6 +1243,15 @@
} finally {
sc.close();
}
+
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - loadWorkspaceScreensDb()", true);
+ ArrayList<String> orderedScreensPairs= new ArrayList<String>();
+ for (Integer i : orderedScreens.keySet()) {
+ orderedScreensPairs.add("{ " + i + ": " + orderedScreens.get(i) + " }");
+ }
+ Launcher.addDumpLog(TAG, "11683562 - screens: " +
+ TextUtils.join(", ", orderedScreensPairs), true);
return orderedScreens;
}
@@ -1508,7 +1525,7 @@
}
if (!added.isEmpty()) {
Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
- addAndBindAddedApps(context, added, cb, null);
+ addAndBindAddedApps(context, added, cb, new ArrayList<AppInfo>());
}
}
@@ -1599,6 +1616,9 @@
/** Returns whether this is an upgradge path */
private boolean loadWorkspace() {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - loadWorkspace()", true);
+
final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
final Context context = mContext;
@@ -1616,8 +1636,12 @@
LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0);
// Check if we need to do any upgrade-path logic
+ // (Includes having just imported default favorites)
boolean loadedOldDb = LauncherAppState.getLauncherProvider().justLoadedOldDb();
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - loadedOldDb: " + loadedOldDb, true);
+
synchronized (sBgLock) {
clearSBgDataStructures();
@@ -1933,6 +1957,10 @@
}
}
Collections.sort(sBgWorkspaceScreens);
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - maxScreenId: " + maxScreenId, true);
+ Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " +
+ TextUtils.join(", ", sBgWorkspaceScreens), true);
LauncherAppState.getLauncherProvider().updateMaxScreenId(maxScreenId);
updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
@@ -1949,6 +1977,9 @@
for (Integer i : orderedScreens.keySet()) {
sBgWorkspaceScreens.add(orderedScreens.get(i));
}
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " +
+ TextUtils.join(", ", sBgWorkspaceScreens), true);
// Remove any empty screens
ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
@@ -1962,6 +1993,10 @@
// If there are any empty screens remove them, and update.
if (unusedScreens.size() != 0) {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - unusedScreens (to be removed): " +
+ TextUtils.join(", ", unusedScreens), true);
+
sBgWorkspaceScreens.removeAll(unusedScreens);
updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
}
@@ -1993,7 +2028,7 @@
/** Filters the set of items who are directly or indirectly (via another container) on the
* specified screen. */
- private void filterCurrentWorkspaceItems(int currentScreen,
+ private void filterCurrentWorkspaceItems(long currentScreenId,
ArrayList<ItemInfo> allWorkspaceItems,
ArrayList<ItemInfo> currentScreenItems,
ArrayList<ItemInfo> otherScreenItems) {
@@ -2006,12 +2041,6 @@
}
}
- // If we aren't filtering on a screen, then the set of items to load is the full set of
- // items given.
- if (currentScreen < 0) {
- currentScreenItems.addAll(allWorkspaceItems);
- }
-
// Order the set of items by their containers first, this allows use to walk through the
// list sequentially, build up a list of containers that are in the specified screen,
// as well as all items in those containers.
@@ -2024,7 +2053,7 @@
});
for (ItemInfo info : allWorkspaceItems) {
if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- if (info.screenId == currentScreen) {
+ if (info.screenId == currentScreenId) {
currentScreenItems.add(info);
itemsOnScreen.add(info.id);
} else {
@@ -2045,20 +2074,15 @@
}
/** Filters the set of widgets which are on the specified screen. */
- private void filterCurrentAppWidgets(int currentScreen,
+ private void filterCurrentAppWidgets(long currentScreenId,
ArrayList<LauncherAppWidgetInfo> appWidgets,
ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
- // If we aren't filtering on a screen, then the set of items to load is the full set of
- // widgets given.
- if (currentScreen < 0) {
- currentScreenWidgets.addAll(appWidgets);
- }
for (LauncherAppWidgetInfo widget : appWidgets) {
if (widget == null) continue;
if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
- widget.screenId == currentScreen) {
+ widget.screenId == currentScreenId) {
currentScreenWidgets.add(widget);
} else {
otherScreenWidgets.add(widget);
@@ -2067,23 +2091,18 @@
}
/** Filters the set of folders which are on the specified screen. */
- private void filterCurrentFolders(int currentScreen,
+ private void filterCurrentFolders(long currentScreenId,
HashMap<Long, ItemInfo> itemsIdMap,
HashMap<Long, FolderInfo> folders,
HashMap<Long, FolderInfo> currentScreenFolders,
HashMap<Long, FolderInfo> otherScreenFolders) {
- // If we aren't filtering on a screen, then the set of items to load is the full set of
- // widgets given.
- if (currentScreen < 0) {
- currentScreenFolders.putAll(folders);
- }
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 == currentScreen) {
+ info.screenId == currentScreenId) {
currentScreenFolders.put(id, folder);
} else {
otherScreenFolders.put(id, folder);
@@ -2210,13 +2229,7 @@
return;
}
- final boolean isLoadingSynchronously = (synchronizeBindPage > -1);
- final int currentScreen = isLoadingSynchronously ? synchronizeBindPage :
- oldCallbacks.getCurrentWorkspaceScreen();
-
- // Load all the items that are on the current page first (and in the process, unbind
- // all the existing workspace items before we call startBinding() below.
- unbindWorkspaceItemsOnMainThread();
+ // Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
ArrayList<LauncherAppWidgetInfo> appWidgets =
new ArrayList<LauncherAppWidgetInfo>();
@@ -2231,6 +2244,22 @@
orderedScreenIds.addAll(sBgWorkspaceScreens);
}
+ final boolean isLoadingSynchronously = (synchronizeBindPage > -1);
+ int currScreen = isLoadingSynchronously ? synchronizeBindPage :
+ oldCallbacks.getCurrentWorkspaceScreen();
+ if (currScreen >= orderedScreenIds.size()) {
+ // There may be no workspace screens (just hotseat items and an empty page).
+ currScreen = -1;
+ }
+ final int currentScreen = currScreen;
+ final long currentScreenId =
+ currentScreen < 0 ? -1 : orderedScreenIds.get(currentScreen);
+
+ // Load all the items that are on the current page first (and in the process, unbind
+ // all the existing workspace items before we call startBinding() below.
+ unbindWorkspaceItemsOnMainThread();
+
+ // Separate the items that are on the current screen, and all the other remaining items
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
@@ -2240,12 +2269,11 @@
HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
- // Separate the items that are on the current screen, and all the other remaining items
- filterCurrentWorkspaceItems(currentScreen, workspaceItems, currentWorkspaceItems,
+ filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
otherWorkspaceItems);
- filterCurrentAppWidgets(currentScreen, appWidgets, currentAppWidgets,
+ filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
otherAppWidgets);
- filterCurrentFolders(currentScreen, itemsIdMap, folders, currentFolders,
+ filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
otherFolders);
sortWorkspaceItemsSpatially(currentWorkspaceItems);
sortWorkspaceItemsSpatially(otherWorkspaceItems);
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index e992706..4eb30e7 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -71,6 +71,9 @@
static final String OLD_AUTHORITY = "com.android.launcher2.settings";
static final String AUTHORITY = ProviderConfig.AUTHORITY;
+ // Should we attempt to load anything from the com.android.launcher2 provider?
+ static final boolean IMPORT_LAUNCHER2_DATABASE = false;
+
static final String TABLE_FAVORITES = "favorites";
static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens";
static final String PARAMETER_NOTIFY = "notify";
@@ -366,32 +369,38 @@
sendAppWidgetResetNotify();
}
- // Try converting the old database
- ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
- public void onRow(ContentValues values) {
- int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
- if (container == Favorites.CONTAINER_DESKTOP) {
- int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
- screen = (int) upgradeLauncherDb_permuteScreens(screen);
- values.put(LauncherSettings.Favorites.SCREEN, screen);
+ if (IMPORT_LAUNCHER2_DATABASE) {
+ // Try converting the old database
+ ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
+ public void onRow(ContentValues values) {
+ int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
+ if (container == Favorites.CONTAINER_DESKTOP) {
+ int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
+ screen = (int) upgradeLauncherDb_permuteScreens(screen);
+ values.put(LauncherSettings.Favorites.SCREEN, screen);
+ }
+ }
+ };
+ Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
+ "/old_favorites?notify=true");
+ if (!convertDatabase(db, uri, permuteScreensCb, true)) {
+ // Try and upgrade from the Launcher2 db
+ uri = LauncherSettings.Favorites.OLD_CONTENT_URI;
+ if (!convertDatabase(db, uri, permuteScreensCb, false)) {
+ // If we fail, then set a flag to load the default workspace
+ setFlagEmptyDbCreated();
+ return;
}
}
- };
- Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
- "/old_favorites?notify=true");
- if (!convertDatabase(db, uri, permuteScreensCb, true)) {
- // Try and upgrade from the Launcher2 db
- uri = LauncherSettings.Favorites.OLD_CONTENT_URI;
- if (!convertDatabase(db, uri, permuteScreensCb, false)) {
- // If we fail, then set a flag to load the default workspace
- setFlagEmptyDbCreated();
- return;
- }
+ // Right now, in non-default workspace cases, we want to run the final
+ // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
+ // set that flag too.
+ setFlagJustLoadedOldDb();
+ } else {
+ // Fresh and clean launcher DB.
+ mMaxItemId = initializeMaxItemId(db);
+ setFlagEmptyDbCreated();
}
- // Right now, in non-default workspace cases, we want to run the final
- // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
- // set that flag too.
- setFlagJustLoadedOldDb();
}
private void addWorkspacesTable(SQLiteDatabase db) {
@@ -868,10 +877,14 @@
throw new RuntimeException("Error: max screen id was not initialized");
}
mMaxScreenId += 1;
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - generateNewScreenId(): " + mMaxScreenId, true);
return mMaxScreenId;
}
public void updateMaxScreenId(long maxScreenId) {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - updateMaxScreenId(): " + maxScreenId, true);
mMaxScreenId = maxScreenId;
}
@@ -892,6 +905,8 @@
throw new RuntimeException("Error: could not query max screen id");
}
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true);
return id;
}
diff --git a/src/com/android/launcher3/LauncherScroller.java b/src/com/android/launcher3/LauncherScroller.java
new file mode 100644
index 0000000..3bd0a78
--- /dev/null
+++ b/src/com/android/launcher3/LauncherScroller.java
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2006 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;
+
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.os.Build;
+import android.util.FloatMath;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+/**
+ * This class differs from the framework {@link android.widget.Scroller} in that
+ * you can modify the Interpolator post-construction.
+ */
+public class LauncherScroller {
+ private int mMode;
+
+ private int mStartX;
+ private int mStartY;
+ private int mFinalX;
+ private int mFinalY;
+
+ private int mMinX;
+ private int mMaxX;
+ private int mMinY;
+ private int mMaxY;
+
+ private int mCurrX;
+ private int mCurrY;
+ private long mStartTime;
+ private int mDuration;
+ private float mDurationReciprocal;
+ private float mDeltaX;
+ private float mDeltaY;
+ private boolean mFinished;
+ private TimeInterpolator mInterpolator;
+ private boolean mFlywheel;
+
+ private float mVelocity;
+ private float mCurrVelocity;
+ private int mDistance;
+
+ private float mFlingFriction = ViewConfiguration.getScrollFriction();
+
+ private static final int DEFAULT_DURATION = 250;
+ private static final int SCROLL_MODE = 0;
+ private static final int FLING_MODE = 1;
+
+ private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
+ private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
+ private static final float START_TENSION = 0.5f;
+ private static final float END_TENSION = 1.0f;
+ private static final float P1 = START_TENSION * INFLEXION;
+ private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
+
+ private static final int NB_SAMPLES = 100;
+ private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
+ private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
+
+ private float mDeceleration;
+ private final float mPpi;
+
+ // A context-specific coefficient adjusted to physical values.
+ private float mPhysicalCoeff;
+
+ static {
+ float x_min = 0.0f;
+ float y_min = 0.0f;
+ for (int i = 0; i < NB_SAMPLES; i++) {
+ final float alpha = (float) i / NB_SAMPLES;
+
+ float x_max = 1.0f;
+ float x, tx, coef;
+ while (true) {
+ x = x_min + (x_max - x_min) / 2.0f;
+ coef = 3.0f * x * (1.0f - x);
+ tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
+ if (Math.abs(tx - alpha) < 1E-5) break;
+ if (tx > alpha) x_max = x;
+ else x_min = x;
+ }
+ SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
+
+ float y_max = 1.0f;
+ float y, dy;
+ while (true) {
+ y = y_min + (y_max - y_min) / 2.0f;
+ coef = 3.0f * y * (1.0f - y);
+ dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
+ if (Math.abs(dy - alpha) < 1E-5) break;
+ if (dy > alpha) y_max = y;
+ else y_min = y;
+ }
+ SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
+ }
+ SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
+
+ // This controls the viscous fluid effect (how much of it)
+ sViscousFluidScale = 8.0f;
+ // must be set to 1.0 (used in viscousFluid())
+ sViscousFluidNormalize = 1.0f;
+ sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
+
+ }
+
+ private static float sViscousFluidScale;
+ private static float sViscousFluidNormalize;
+
+ public void setInterpolator(TimeInterpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ /**
+ * Create a Scroller with the default duration and interpolator.
+ */
+ public LauncherScroller(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Create a Scroller with the specified interpolator. If the interpolator is
+ * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
+ * be in effect for apps targeting Honeycomb or newer.
+ */
+ public LauncherScroller(Context context, Interpolator interpolator) {
+ this(context, interpolator,
+ context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
+ }
+
+ /**
+ * Create a Scroller with the specified interpolator. If the interpolator is
+ * null, the default (viscous) interpolator will be used. Specify whether or
+ * not to support progressive "flywheel" behavior in flinging.
+ */
+ public LauncherScroller(Context context, Interpolator interpolator, boolean flywheel) {
+ mFinished = true;
+ mInterpolator = interpolator;
+ mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
+ mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
+ mFlywheel = flywheel;
+
+ mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
+ }
+
+ /**
+ * The amount of friction applied to flings. The default value
+ * is {@link ViewConfiguration#getScrollFriction}.
+ *
+ * @param friction A scalar dimension-less value representing the coefficient of
+ * friction.
+ */
+ public final void setFriction(float friction) {
+ mDeceleration = computeDeceleration(friction);
+ mFlingFriction = friction;
+ }
+
+ private float computeDeceleration(float friction) {
+ return SensorManager.GRAVITY_EARTH // g (m/s^2)
+ * 39.37f // inch/meter
+ * mPpi // pixels per inch
+ * friction;
+ }
+
+ /**
+ *
+ * Returns whether the scroller has finished scrolling.
+ *
+ * @return True if the scroller has finished scrolling, false otherwise.
+ */
+ public final boolean isFinished() {
+ return mFinished;
+ }
+
+ /**
+ * Force the finished field to a particular value.
+ *
+ * @param finished The new finished value.
+ */
+ public final void forceFinished(boolean finished) {
+ mFinished = finished;
+ }
+
+ /**
+ * Returns how long the scroll event will take, in milliseconds.
+ *
+ * @return The duration of the scroll in milliseconds.
+ */
+ public final int getDuration() {
+ return mDuration;
+ }
+
+ /**
+ * Returns the current X offset in the scroll.
+ *
+ * @return The new X offset as an absolute distance from the origin.
+ */
+ public final int getCurrX() {
+ return mCurrX;
+ }
+
+ /**
+ * Returns the current Y offset in the scroll.
+ *
+ * @return The new Y offset as an absolute distance from the origin.
+ */
+ public final int getCurrY() {
+ return mCurrY;
+ }
+
+ /**
+ * Returns the current velocity.
+ *
+ * @return The original velocity less the deceleration. Result may be
+ * negative.
+ */
+ public float getCurrVelocity() {
+ return mMode == FLING_MODE ?
+ mCurrVelocity : mVelocity - mDeceleration * timePassed() / 2000.0f;
+ }
+
+ /**
+ * Returns the start X offset in the scroll.
+ *
+ * @return The start X offset as an absolute distance from the origin.
+ */
+ public final int getStartX() {
+ return mStartX;
+ }
+
+ /**
+ * Returns the start Y offset in the scroll.
+ *
+ * @return The start Y offset as an absolute distance from the origin.
+ */
+ public final int getStartY() {
+ return mStartY;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final X offset as an absolute distance from the origin.
+ */
+ public final int getFinalX() {
+ return mFinalX;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final Y offset as an absolute distance from the origin.
+ */
+ public final int getFinalY() {
+ return mFinalY;
+ }
+
+ /**
+ * Call this when you want to know the new location. If it returns true,
+ * the animation is not yet finished.
+ */
+ public boolean computeScrollOffset() {
+ if (mFinished) {
+ return false;
+ }
+
+ int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+
+ if (timePassed < mDuration) {
+ switch (mMode) {
+ case SCROLL_MODE:
+ float x = timePassed * mDurationReciprocal;
+
+ if (mInterpolator == null)
+ x = viscousFluid(x);
+ else
+ x = mInterpolator.getInterpolation(x);
+
+ mCurrX = mStartX + Math.round(x * mDeltaX);
+ mCurrY = mStartY + Math.round(x * mDeltaY);
+ break;
+ case FLING_MODE:
+ final float t = (float) timePassed / mDuration;
+ final int index = (int) (NB_SAMPLES * t);
+ float distanceCoef = 1.f;
+ float velocityCoef = 0.f;
+ if (index < NB_SAMPLES) {
+ final float t_inf = (float) index / NB_SAMPLES;
+ final float t_sup = (float) (index + 1) / NB_SAMPLES;
+ final float d_inf = SPLINE_POSITION[index];
+ final float d_sup = SPLINE_POSITION[index + 1];
+ velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
+ distanceCoef = d_inf + (t - t_inf) * velocityCoef;
+ }
+
+ mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
+
+ mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
+ // Pin to mMinX <= mCurrX <= mMaxX
+ mCurrX = Math.min(mCurrX, mMaxX);
+ mCurrX = Math.max(mCurrX, mMinX);
+
+ mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
+ // Pin to mMinY <= mCurrY <= mMaxY
+ mCurrY = Math.min(mCurrY, mMaxY);
+ mCurrY = Math.max(mCurrY, mMinY);
+
+ if (mCurrX == mFinalX && mCurrY == mFinalY) {
+ mFinished = true;
+ }
+
+ break;
+ }
+ }
+ else {
+ mCurrX = mFinalX;
+ mCurrY = mFinalY;
+ mFinished = true;
+ }
+ return true;
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ * The scroll will use the default value of 250 milliseconds for the
+ * duration.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy) {
+ startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
+ }
+
+ /**
+ * Start scrolling by providing a starting point, the distance to travel,
+ * and the duration of the scroll.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ * @param duration Duration of the scroll in milliseconds.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy, int duration) {
+ mMode = SCROLL_MODE;
+ mFinished = false;
+ mDuration = duration;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartX = startX;
+ mStartY = startY;
+ mFinalX = startX + dx;
+ mFinalY = startY + dy;
+ mDeltaX = dx;
+ mDeltaY = dy;
+ mDurationReciprocal = 1.0f / (float) mDuration;
+ }
+
+ /**
+ * Start scrolling based on a fling gesture. The distance travelled will
+ * depend on the initial velocity of the fling.
+ *
+ * @param startX Starting point of the scroll (X)
+ * @param startY Starting point of the scroll (Y)
+ * @param velocityX Initial velocity of the fling (X) measured in pixels per
+ * second.
+ * @param velocityY Initial velocity of the fling (Y) measured in pixels per
+ * second
+ * @param minX Minimum X value. The scroller will not scroll past this
+ * point.
+ * @param maxX Maximum X value. The scroller will not scroll past this
+ * point.
+ * @param minY Minimum Y value. The scroller will not scroll past this
+ * point.
+ * @param maxY Maximum Y value. The scroller will not scroll past this
+ * point.
+ */
+ public void fling(int startX, int startY, int velocityX, int velocityY,
+ int minX, int maxX, int minY, int maxY) {
+ // Continue a scroll or fling in progress
+ if (mFlywheel && !mFinished) {
+ float oldVel = getCurrVelocity();
+
+ float dx = (float) (mFinalX - mStartX);
+ float dy = (float) (mFinalY - mStartY);
+ float hyp = FloatMath.sqrt(dx * dx + dy * dy);
+
+ float ndx = dx / hyp;
+ float ndy = dy / hyp;
+
+ float oldVelocityX = ndx * oldVel;
+ float oldVelocityY = ndy * oldVel;
+ if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
+ Math.signum(velocityY) == Math.signum(oldVelocityY)) {
+ velocityX += oldVelocityX;
+ velocityY += oldVelocityY;
+ }
+ }
+
+ mMode = FLING_MODE;
+ mFinished = false;
+
+ float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
+
+ mVelocity = velocity;
+ mDuration = getSplineFlingDuration(velocity);
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartX = startX;
+ mStartY = startY;
+
+ float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
+ float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
+
+ double totalDistance = getSplineFlingDistance(velocity);
+ mDistance = (int) (totalDistance * Math.signum(velocity));
+
+ mMinX = minX;
+ mMaxX = maxX;
+ mMinY = minY;
+ mMaxY = maxY;
+
+ mFinalX = startX + (int) Math.round(totalDistance * coeffX);
+ // Pin to mMinX <= mFinalX <= mMaxX
+ mFinalX = Math.min(mFinalX, mMaxX);
+ mFinalX = Math.max(mFinalX, mMinX);
+
+ mFinalY = startY + (int) Math.round(totalDistance * coeffY);
+ // Pin to mMinY <= mFinalY <= mMaxY
+ mFinalY = Math.min(mFinalY, mMaxY);
+ mFinalY = Math.max(mFinalY, mMinY);
+ }
+
+ private double getSplineDeceleration(float velocity) {
+ return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
+ }
+
+ private int getSplineFlingDuration(float velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return (int) (1000.0 * Math.exp(l / decelMinusOne));
+ }
+
+ private double getSplineFlingDistance(float velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
+ }
+
+ static float viscousFluid(float x)
+ {
+ x *= sViscousFluidScale;
+ if (x < 1.0f) {
+ x -= (1.0f - (float)Math.exp(-x));
+ } else {
+ float start = 0.36787944117f; // 1/e == exp(-1)
+ x = 1.0f - (float)Math.exp(1.0f - x);
+ x = start + x * (1.0f - start);
+ }
+ x *= sViscousFluidNormalize;
+ return x;
+ }
+
+ /**
+ * Stops the animation. Contrary to {@link #forceFinished(boolean)},
+ * aborting the animating cause the scroller to move to the final x and y
+ * position
+ *
+ * @see #forceFinished(boolean)
+ */
+ public void abortAnimation() {
+ mCurrX = mFinalX;
+ mCurrY = mFinalY;
+ mFinished = true;
+ }
+
+ /**
+ * Extend the scroll animation. This allows a running animation to scroll
+ * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
+ *
+ * @param extend Additional time to scroll in milliseconds.
+ * @see #setFinalX(int)
+ * @see #setFinalY(int)
+ */
+ public void extendDuration(int extend) {
+ int passed = timePassed();
+ mDuration = passed + extend;
+ mDurationReciprocal = 1.0f / mDuration;
+ mFinished = false;
+ }
+
+ /**
+ * Returns the time elapsed since the beginning of the scrolling.
+ *
+ * @return The elapsed time in milliseconds.
+ */
+ public int timePassed() {
+ return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+ }
+
+ /**
+ * Sets the final position (X) for this scroller.
+ *
+ * @param newX The new X offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalY(int)
+ */
+ public void setFinalX(int newX) {
+ mFinalX = newX;
+ mDeltaX = mFinalX - mStartX;
+ mFinished = false;
+ }
+
+ /**
+ * Sets the final position (Y) for this scroller.
+ *
+ * @param newY The new Y offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalX(int)
+ */
+ public void setFinalY(int newY) {
+ mFinalY = newY;
+ mDeltaY = mFinalY - mStartY;
+ mFinished = false;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isScrollingInDirection(float xvel, float yvel) {
+ return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
+ Math.signum(yvel) == Math.signum(mFinalY - mStartY);
+ }
+}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 3eecedb..2c7114b 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -122,7 +122,8 @@
protected int mNextPage = INVALID_PAGE;
protected int mMaxScrollX;
- protected Scroller mScroller;
+ protected LauncherScroller mScroller;
+ private Interpolator mDefaultInterpolator;
private VelocityTracker mVelocityTracker;
private int mPageSpacing = 0;
@@ -152,7 +153,6 @@
protected int mTouchState = TOUCH_STATE_REST;
protected boolean mForceScreenScrolled = false;
- private boolean mScrollAbortedFromIntercept = false;
protected OnLongClickListener mLongClickListener;
@@ -310,7 +310,8 @@
protected void init() {
mDirtyPageContent = new ArrayList<Boolean>();
mDirtyPageContent.ensureCapacity(32);
- mScroller = new Scroller(getContext(), new ScrollInterpolator());
+ mScroller = new LauncherScroller(getContext());
+ setDefaultInterpolator(new ScrollInterpolator());
mCurrentPage = 0;
mCenterPagesVertically = true;
@@ -330,6 +331,11 @@
setOnHierarchyChangeListener(this);
}
+ protected void setDefaultInterpolator(Interpolator interpolator) {
+ mDefaultInterpolator = interpolator;
+ mScroller.setInterpolator(mDefaultInterpolator);
+ }
+
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -564,6 +570,9 @@
void setRestorePage(int restorePage) {
mRestorePage = restorePage;
}
+ int getRestorePage() {
+ return mRestorePage;
+ }
protected void notifyPageSwitchListener() {
if (mPageSwitchListener != null) {
@@ -1385,13 +1394,13 @@
* being flinged.
*/
final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
- final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
+ final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
if (finishedScrolling) {
mTouchState = TOUCH_STATE_REST;
if (!mScroller.isFinished()) {
- mScrollAbortedFromIntercept = true;
- abortScrollerAnimation(false);
+ setCurrentPage(getNextPage());
+ pageEndMoving();
}
} else {
if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
@@ -1419,9 +1428,6 @@
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- if (mScrollAbortedFromIntercept) {
- snapToDestination();
- }
resetTouchState();
break;
@@ -1625,11 +1631,11 @@
}
protected void enableFreeScroll() {
- setEnableFreeScroll(true, -1);
+ setEnableFreeScroll(true);
}
- protected void disableFreeScroll(int snapPage) {
- setEnableFreeScroll(false, snapPage);
+ protected void disableFreeScroll() {
+ setEnableFreeScroll(false);
}
void updateFreescrollBounds() {
@@ -1643,16 +1649,10 @@
}
}
- private void setEnableFreeScroll(boolean freeScroll, int snapPage) {
+ private void setEnableFreeScroll(boolean freeScroll) {
mFreeScroll = freeScroll;
- if (snapPage == -1) {
- snapPage = getPageNearestToCenterOfScreen();
- }
-
- if (!mFreeScroll) {
- snapToPage(snapPage);
- } else {
+ if (mFreeScroll) {
updateFreescrollBounds();
getOverviewModePages(mTempVisiblePagesRange);
if (getCurrentPage() < mTempVisiblePagesRange[0]) {
@@ -1910,6 +1910,7 @@
int vX = (int) (-velocityX * scaleX);
int initialScrollX = (int) (getScrollX() * scaleX);
+ mScroller.setInterpolator(mDefaultInterpolator);
mScroller.fling(initialScrollX,
getScrollY(), vX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
invalidate();
@@ -1995,7 +1996,6 @@
releaseVelocityTracker();
endReordering();
mCancelTap = false;
- mScrollAbortedFromIntercept = false;
mTouchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
}
@@ -2176,27 +2176,33 @@
}
protected void snapToPageImmediately(int whichPage) {
- snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true);
+ snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true, null);
}
protected void snapToPage(int whichPage, int duration) {
- snapToPage(whichPage, duration, false);
+ snapToPage(whichPage, duration, false, null);
}
- protected void snapToPage(int whichPage, int duration, boolean immediate) {
+ protected void snapToPage(int whichPage, int duration, TimeInterpolator interpolator) {
+ snapToPage(whichPage, duration, false, interpolator);
+ }
+
+ protected void snapToPage(int whichPage, int duration, boolean immediate,
+ TimeInterpolator interpolator) {
whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
int newX = getScrollForPage(whichPage);
final int sX = mUnboundedScrollX;
final int delta = newX - sX;
- snapToPage(whichPage, delta, duration, immediate);
+ snapToPage(whichPage, delta, duration, immediate, interpolator);
}
protected void snapToPage(int whichPage, int delta, int duration) {
- snapToPage(whichPage, delta, duration, false);
+ snapToPage(whichPage, delta, duration, false, null);
}
- protected void snapToPage(int whichPage, int delta, int duration, boolean immediate) {
+ protected void snapToPage(int whichPage, int delta, int duration, boolean immediate,
+ TimeInterpolator interpolator) {
mNextPage = whichPage;
View focusedChild = getFocusedChild();
if (focusedChild != null && whichPage != mCurrentPage &&
@@ -2215,8 +2221,15 @@
}
if (!mScroller.isFinished()) {
- mScroller.abortAnimation();
+ abortScrollerAnimation(false);
}
+
+ if (interpolator != null) {
+ mScroller.setInterpolator(interpolator);
+ } else {
+ mScroller.setInterpolator(mDefaultInterpolator);
+ }
+
mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
notifyPageSwitchListener();
@@ -2472,7 +2485,8 @@
mDragView = getChildAt(dragViewIndex);
mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start();
mDragViewBaselineLeft = mDragView.getLeft();
- disableFreeScroll(-1);
+ snapToPage(getPageNearestToCenterOfScreen());
+ disableFreeScroll();
onStartReordering();
return true;
}
diff --git a/src/com/android/launcher3/RampUpScroller.java b/src/com/android/launcher3/RampUpScroller.java
deleted file mode 100644
index 89eb579..0000000
--- a/src/com/android/launcher3/RampUpScroller.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2013 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;
-
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-
-/**
- * Scroller that gradually reaches a target velocity.
- */
-class RampUpScroller {
- private final Interpolator mInterpolator;
- private final long mRampUpTime;
-
- private long mStartTime;
- private long mDeltaTime;
- private float mTargetVelocityX;
- private float mTargetVelocityY;
- private int mDeltaX;
- private int mDeltaY;
-
- /**
- * Creates a new ramp-up scroller that reaches full velocity after a
- * specified duration.
- *
- * @param rampUpTime Duration before the scroller reaches target velocity.
- */
- public RampUpScroller(long rampUpTime) {
- mInterpolator = new AccelerateInterpolator();
- mRampUpTime = rampUpTime;
- }
-
- /**
- * Starts the scroller at the current animation time.
- */
- public void start() {
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
- mDeltaTime = mStartTime;
- }
-
- /**
- * Computes the current scroll deltas. This usually only be called after
- * starting the scroller with {@link #start()}.
- *
- * @see #getDeltaX()
- * @see #getDeltaY()
- */
- public void computeScrollDelta() {
- final long currentTime = AnimationUtils.currentAnimationTimeMillis();
- final long elapsedSinceStart = currentTime - mStartTime;
- final float scale;
- if (elapsedSinceStart < mRampUpTime) {
- scale = mInterpolator.getInterpolation((float) elapsedSinceStart / mRampUpTime);
- } else {
- scale = 1f;
- }
-
- final long elapsedSinceDelta = currentTime - mDeltaTime;
- mDeltaTime = currentTime;
-
- mDeltaX = (int) (elapsedSinceDelta * scale * mTargetVelocityX);
- mDeltaY = (int) (elapsedSinceDelta * scale * mTargetVelocityY);
- }
-
- /**
- * Sets the target velocity for this scroller.
- *
- * @param x The target X velocity in pixels per millisecond.
- * @param y The target Y velocity in pixels per millisecond.
- */
- public void setTargetVelocity(float x, float y) {
- mTargetVelocityX = x;
- mTargetVelocityY = y;
- }
-
- /**
- * @return The target X velocity for this scroller.
- */
- public float getTargetVelocityX() {
- return mTargetVelocityX;
- }
-
- /**
- * @return The target Y velocity for this scroller.
- */
- public float getTargetVelocityY() {
- return mTargetVelocityY;
- }
-
- /**
- * The distance traveled in the X-coordinate computed by the last call to
- * {@link #computeScrollDelta()}.
- */
- public int getDeltaX() {
- return mDeltaX;
- }
-
- /**
- * The distance traveled in the Y-coordinate computed by the last call to
- * {@link #computeScrollDelta()}.
- */
- public int getDeltaY() {
- return mDeltaY;
- }
-}
diff --git a/src/com/android/launcher3/SmoothPagedView.java b/src/com/android/launcher3/SmoothPagedView.java
index 64dcb34..4e331aa 100644
--- a/src/com/android/launcher3/SmoothPagedView.java
+++ b/src/com/android/launcher3/SmoothPagedView.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.util.AttributeSet;
import android.view.animation.Interpolator;
-import android.widget.Scroller;
public abstract class SmoothPagedView extends PagedView {
private static final float SMOOTHING_SPEED = 0.75f;
@@ -100,7 +99,7 @@
mBaseLineFlingVelocity = 2500.0f;
mFlingVelocityInfluence = 0.4f;
mScrollInterpolator = new OvershootInterpolator();
- mScroller = new Scroller(getContext(), mScrollInterpolator);
+ setDefaultInterpolator(mScrollInterpolator);
}
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 6c346c4..b8af9ad 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -31,7 +31,6 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -55,10 +54,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.View.OnClickListener;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.TextView;
@@ -338,6 +334,14 @@
@Override
public void setInsets(Rect insets) {
mInsets.set(insets);
+
+ CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
+ if (customScreen != null) {
+ View customContent = customScreen.getShortcutsAndWidgets().getChildAt(0);
+ if (customContent instanceof Insettable) {
+ ((Insettable) customContent).setInsets(mInsets);
+ }
+ }
}
// estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
@@ -534,6 +538,10 @@
}
public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - insertNewWorkspaceScreen(): " + screenId +
+ " at index: " + insertIndex, true);
+
if (mWorkspaceScreens.containsKey(screenId)) {
throw new RuntimeException("Screen id " + screenId + " already exists!");
}
@@ -619,6 +627,12 @@
if (customContent instanceof Insettable) {
((Insettable)customContent).setInsets(mInsets);
}
+
+ // Verify that the child is removed from any existing parent.
+ if (customContent.getParent() instanceof ViewGroup) {
+ ViewGroup parent = (ViewGroup) customContent.getParent();
+ parent.removeView(customContent);
+ }
customScreen.removeAllViews();
customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
mCustomContentDescription = description;
@@ -627,6 +641,9 @@
}
public void addExtraEmptyScreenOnDrag() {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreenOnDrag()", true);
+
boolean lastChildOnScreen = false;
boolean childOnFinalScreen = false;
@@ -653,6 +670,9 @@
}
public boolean addExtraEmptyScreen() {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreen()", true);
+
if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
return true;
@@ -661,6 +681,9 @@
}
private void convertFinalScreenToEmptyScreenIfNecessary() {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - convertFinalScreenToEmptyScreenIfNecessary()", true);
+
if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
@@ -676,6 +699,10 @@
// if this is the last non-custom content screen, convert it to the empty screen
mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
+
+ // Update the model if we have changed any screens
+ mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
+ Launcher.addDumpLog(TAG, "11683562 - extra empty screen: " + finalScreenId, true);
}
}
@@ -685,6 +712,8 @@
public void removeExtraEmptyScreen(final boolean animate, final Runnable onComplete,
final int delay, final boolean stripEmptyScreens) {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - removeExtraEmptyScreen()", true);
if (delay > 0) {
postDelayed(new Runnable() {
@Override
@@ -708,7 +737,12 @@
onComplete, stripEmptyScreens);
}
return;
+ } else if (stripEmptyScreens) {
+ // If we're not going to strip the empty screens after removing
+ // the extra empty screen, do it right away.
+ stripEmptyScreens();
}
+
if (onComplete != null) {
onComplete.run();
}
@@ -716,6 +750,9 @@
private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
final boolean stripEmptyScreens) {
+ // Log to disk
+ // XXX: Do we need to update LM workspace screens below?
+ Launcher.addDumpLog(TAG, "11683562 - fadeAndRemoveEmptyScreen()", true);
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
@@ -759,6 +796,9 @@
}
public long commitExtraEmptyScreen() {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - commitExtraEmptyScreen()", true);
+
int index = getPageIndexForScreenId(EXTRA_EMPTY_SCREEN_ID);
CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
@@ -811,6 +851,9 @@
}
public void stripEmptyScreens() {
+ // Log to disk
+ Launcher.addDumpLog(TAG, "11683562 - stripEmptyScreens()", true);
+
if (isPageMoving()) {
mStripScreensOnPageStopMoving = true;
return;
@@ -831,6 +874,7 @@
int pageShift = 0;
for (Long id: removeScreens) {
+ Launcher.addDumpLog(TAG, "11683562 - removing id: " + id, true);
CellLayout cl = mWorkspaceScreens.get(id);
mWorkspaceScreens.remove(id);
mScreenOrder.remove(id);
@@ -949,7 +993,9 @@
}
// Get the canonical child id to uniquely represent this view in this screen
- int childId = LauncherModel.getCellLayoutChildId(container, screenId, x, y, spanX, spanY);
+ ItemInfo info = (ItemInfo) child.getTag();
+ int childId = mLauncher.getViewIdForItem(info);
+
boolean markCellsAsOccupied = !(child instanceof Folder);
if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
// TODO: This branch occurs when the workspace is adding views
@@ -2073,7 +2119,7 @@
mNewScale = 1.0f;
if (oldStateIsOverview) {
- disableFreeScroll(snapPage);
+ disableFreeScroll();
} else if (stateIsOverview) {
enableFreeScroll();
}
@@ -2100,15 +2146,20 @@
duration = getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
}
+ if (snapPage == -1) {
+ snapPage = getPageNearestToCenterOfScreen();
+ }
+ snapToPage(snapPage, duration, mZoomInInterpolator);
+
for (int i = 0; i < getChildCount(); i++) {
final CellLayout cl = (CellLayout) getChildAt(i);
- boolean isCurrentPage = (i == getNextPage());
+ boolean isCurrentPage = (i == snapPage);
float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
float finalAlpha;
if (stateIsSmall) {
finalAlpha = 0f;
} else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
- finalAlpha = i == getNextPage() ? 1f : 0f;
+ finalAlpha = (i == snapPage || i < numCustomPages()) ? 1f : 0f;
} else {
finalAlpha = 1f;
}
@@ -2140,6 +2191,7 @@
final View searchBar = mLauncher.getQsbBar();
final View overviewPanel = mLauncher.getOverviewPanel();
final View hotseat = mLauncher.getHotseat();
+ final View pageIndicator = getPageIndicator();
if (animated) {
anim.setDuration(duration);
LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
@@ -2179,31 +2231,36 @@
}
}
}
- ObjectAnimator pageIndicatorAlpha = null;
- if (getPageIndicator() != null) {
- pageIndicatorAlpha = ObjectAnimator.ofFloat(getPageIndicator(), "alpha",
- finalHotseatAndPageIndicatorAlpha);
+ Animator pageIndicatorAlpha = null;
+ if (pageIndicator != null) {
+ pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator)
+ .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
+ } else {
+ // create a dummy animation so we don't need to do null checks later
+ pageIndicatorAlpha = ValueAnimator.ofFloat(0, 0);
}
- ObjectAnimator hotseatAlpha = ObjectAnimator.ofFloat(hotseat, "alpha",
- finalHotseatAndPageIndicatorAlpha);
- ObjectAnimator searchBarAlpha = ObjectAnimator.ofFloat(searchBar,
- "alpha", finalSearchBarAlpha);
- ObjectAnimator overviewPanelAlpha = ObjectAnimator.ofFloat(overviewPanel,
- "alpha", finalOverviewPanelAlpha);
+ Animator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat)
+ .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
+ Animator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar)
+ .alpha(finalSearchBarAlpha).withLayer();
+ Animator overviewPanelAlpha = new LauncherViewPropertyAnimator(overviewPanel)
+ .alpha(finalOverviewPanelAlpha).withLayer();
- overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
+ pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator));
hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
+ overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
if (workspaceToOverview) {
+ pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
+ overviewPanelAlpha.setInterpolator(null);
} else if (overviewToWorkspace) {
+ pageIndicatorAlpha.setInterpolator(null);
+ hotseatAlpha.setInterpolator(null);
overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
}
-
- if (getPageIndicator() != null) {
- pageIndicatorAlpha.addListener(new AlphaUpdateListener(getPageIndicator()));
- }
+ searchBarAlpha.setInterpolator(null);
anim.play(overviewPanelAlpha);
anim.play(hotseatAlpha);
@@ -2215,9 +2272,9 @@
AlphaUpdateListener.updateVisibility(overviewPanel);
hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
AlphaUpdateListener.updateVisibility(hotseat);
- if (getPageIndicator() != null) {
- getPageIndicator().setAlpha(finalHotseatAndPageIndicatorAlpha);
- AlphaUpdateListener.updateVisibility(getPageIndicator());
+ if (pageIndicator != null) {
+ pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
+ AlphaUpdateListener.updateVisibility(pageIndicator);
}
searchBar.setAlpha(finalSearchBarAlpha);
AlphaUpdateListener.updateVisibility(searchBar);
@@ -2333,7 +2390,9 @@
void hideCustomContentIfNecessary() {
boolean hide = mState != Workspace.State.NORMAL;
if (hide && hasCustomContent()) {
+ disableLayoutTransitions();
mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
+ enableLayoutTransitions();
}
}
@@ -2349,6 +2408,11 @@
final CellLayout cl = (CellLayout) getChildAt(i);
cl.setShortcutAndWidgetAlpha(1f);
}
+ } else {
+ for (int i = 0; i < numCustomPages(); i++) {
+ final CellLayout cl = (CellLayout) getChildAt(i);
+ cl.setShortcutAndWidgetAlpha(1f);
+ }
}
showCustomContentIfNecessary();
}
@@ -2543,8 +2607,9 @@
throw new IllegalStateException(msg);
}
- mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
+ DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
+ dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
if (child.getParent() instanceof ShortcutAndWidgetContainer) {
mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
@@ -2890,8 +2955,6 @@
lp.cellHSpan = item.spanX;
lp.cellVSpan = item.spanY;
lp.isLockedToGrid = true;
- cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screenId,
- mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
cell instanceof LauncherAppWidgetHostView) {
@@ -3914,7 +3977,7 @@
return mDragInfo;
}
- public int getRestorePage() {
+ public int getCurrentPageOffsetFromCustomContent() {
return getNextPage() - numCustomPages();
}
@@ -4148,6 +4211,11 @@
}
@Override
+ public float getIntrinsicIconScaleFactor() {
+ return 1f;
+ }
+
+ @Override
public boolean supportsFlingToDelete() {
return true;
}
@@ -4568,6 +4636,7 @@
child.requestFocus();
}
}
+ exitWidgetResizeMode();
}
@Override