Many changes are required to make scalable displays work correctly on foldables. This first one is making sure that the correct number of columns is used when calculating the used width for scalable grid. The spaces around the workspace are not final yet. Bug: 191879424 Test: manual Change-Id: Idc41ed004580f1f86d8f9595d005abc72301b1e3
1215 lines
54 KiB
Java
1215 lines
54 KiB
Java
/*
|
|
* 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 static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
|
|
|
|
import static com.android.launcher3.ResourceUtils.pxFromDp;
|
|
import static com.android.launcher3.Utilities.dpiFromPx;
|
|
import static com.android.launcher3.Utilities.pxFromSp;
|
|
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
|
|
import static com.android.launcher3.util.WindowManagerCompat.MIN_TABLET_WIDTH;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.content.Context;
|
|
import android.content.res.Configuration;
|
|
import android.content.res.Resources;
|
|
import android.graphics.Path;
|
|
import android.graphics.Point;
|
|
import android.graphics.PointF;
|
|
import android.graphics.Rect;
|
|
import android.util.DisplayMetrics;
|
|
import android.util.Pair;
|
|
import android.view.Surface;
|
|
|
|
import com.android.launcher3.CellLayout.ContainerType;
|
|
import com.android.launcher3.DevicePaddings.DevicePadding;
|
|
import com.android.launcher3.config.FeatureFlags;
|
|
import com.android.launcher3.icons.DotRenderer;
|
|
import com.android.launcher3.icons.GraphicsUtils;
|
|
import com.android.launcher3.icons.IconNormalizer;
|
|
import com.android.launcher3.uioverrides.ApiWrapper;
|
|
import com.android.launcher3.util.DisplayController;
|
|
import com.android.launcher3.util.DisplayController.Info;
|
|
import com.android.launcher3.util.WindowBounds;
|
|
|
|
import java.io.PrintWriter;
|
|
|
|
@SuppressLint("NewApi")
|
|
public class DeviceProfile {
|
|
|
|
private static final int DEFAULT_DOT_SIZE = 100;
|
|
// Ratio of empty space, qsb should take up to appear visually centered.
|
|
private static final float QSB_CENTER_FACTOR = .325f;
|
|
|
|
public final InvariantDeviceProfile inv;
|
|
private final Info mInfo;
|
|
private final DisplayMetrics mMetrics;
|
|
|
|
// Device properties
|
|
public final boolean isTablet;
|
|
public final boolean isPhone;
|
|
public final boolean transposeLayoutWithOrientation;
|
|
public final boolean isTwoPanels;
|
|
public final boolean allowRotation;
|
|
|
|
// Device properties in current orientation
|
|
public final boolean isLandscape;
|
|
public final boolean isMultiWindowMode;
|
|
|
|
public final int windowX;
|
|
public final int windowY;
|
|
public final int widthPx;
|
|
public final int heightPx;
|
|
public final int availableWidthPx;
|
|
public final int availableHeightPx;
|
|
|
|
public final float aspectRatio;
|
|
|
|
public final boolean isScalableGrid;
|
|
|
|
/**
|
|
* The maximum amount of left/right workspace padding as a percentage of the screen width.
|
|
* To be clear, this means that up to 7% of the screen width can be used as left padding, and
|
|
* 7% of the screen width can be used as right padding.
|
|
*/
|
|
private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f;
|
|
|
|
private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
|
|
private static final float TALLER_DEVICE_ASPECT_RATIO_THRESHOLD = 2.15f;
|
|
private static final float TALL_DEVICE_EXTRA_SPACE_THRESHOLD_DP = 252;
|
|
private static final float TALL_DEVICE_MORE_EXTRA_SPACE_THRESHOLD_DP = 268;
|
|
|
|
// To evenly space the icons, increase the left/right margins for tablets in portrait mode.
|
|
private static final int PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER = 4;
|
|
|
|
// Workspace
|
|
public final int desiredWorkspaceLeftRightOriginalPx;
|
|
public int desiredWorkspaceLeftRightMarginPx;
|
|
public final int cellLayoutBorderSpacingOriginalPx;
|
|
public int cellLayoutBorderSpacingPx;
|
|
public final int cellLayoutPaddingLeftRightPx;
|
|
public final int cellLayoutBottomPaddingPx;
|
|
public final int edgeMarginPx;
|
|
public float workspaceSpringLoadShrinkFactor;
|
|
public final int workspaceSpringLoadedBottomSpace;
|
|
|
|
private final int extraSpace;
|
|
public int workspaceTopPadding;
|
|
public int workspaceBottomPadding;
|
|
public int extraHotseatBottomPadding;
|
|
|
|
// Workspace page indicator
|
|
public final int workspacePageIndicatorHeight;
|
|
private final int mWorkspacePageIndicatorOverlapWorkspace;
|
|
|
|
// Workspace icons
|
|
public float iconScale;
|
|
public int iconSizePx;
|
|
public int iconTextSizePx;
|
|
public int iconDrawablePaddingPx;
|
|
public int iconDrawablePaddingOriginalPx;
|
|
|
|
public float cellScaleToFit;
|
|
public int cellWidthPx;
|
|
public int cellHeightPx;
|
|
public int workspaceCellPaddingXPx;
|
|
|
|
public int cellYPaddingPx;
|
|
|
|
// Folder
|
|
public float folderLabelTextScale;
|
|
public int folderLabelTextSizePx;
|
|
public int folderIconSizePx;
|
|
public int folderIconOffsetYPx;
|
|
|
|
// Folder content
|
|
public int folderCellLayoutBorderSpacingPx;
|
|
public int folderContentPaddingLeftRight;
|
|
public int folderContentPaddingTop;
|
|
|
|
// Folder cell
|
|
public int folderCellWidthPx;
|
|
public int folderCellHeightPx;
|
|
|
|
// Folder child
|
|
public int folderChildIconSizePx;
|
|
public int folderChildTextSizePx;
|
|
public int folderChildDrawablePaddingPx;
|
|
|
|
// Hotseat
|
|
public int hotseatBarSizeExtraSpacePx;
|
|
public final int numShownHotseatIcons;
|
|
public int hotseatCellHeightPx;
|
|
private final int hotseatExtraVerticalSize;
|
|
// In portrait: size = height, in landscape: size = width
|
|
public int hotseatBarSizePx;
|
|
public int hotseatBarTopPaddingPx;
|
|
public final int hotseatBarBottomPaddingPx;
|
|
// Start is the side next to the nav bar, end is the side next to the workspace
|
|
public final int hotseatBarSidePaddingStartPx;
|
|
public final int hotseatBarSidePaddingEndPx;
|
|
public final int hotseatQsbHeight;
|
|
|
|
public final float qsbBottomMarginOriginalPx;
|
|
public int qsbBottomMarginPx;
|
|
|
|
// All apps
|
|
public int allAppsOpenVerticalTranslate;
|
|
public int allAppsCellHeightPx;
|
|
public int allAppsCellWidthPx;
|
|
public int allAppsIconSizePx;
|
|
public int allAppsIconDrawablePaddingPx;
|
|
public final int numShownAllAppsColumns;
|
|
public float allAppsIconTextSizePx;
|
|
|
|
// Overview
|
|
public final boolean overviewShowAsGrid;
|
|
public int overviewTaskMarginPx;
|
|
public int overviewTaskMarginGridPx;
|
|
public int overviewTaskIconSizePx;
|
|
public int overviewTaskIconDrawableSizePx;
|
|
public int overviewTaskIconDrawableSizeGridPx;
|
|
public int overviewTaskThumbnailTopMarginPx;
|
|
public final int overviewActionsMarginThreeButtonPx;
|
|
public final int overviewActionsTopMarginGesturePx;
|
|
public final int overviewActionsBottomMarginGesturePx;
|
|
public int overviewPageSpacing;
|
|
public int overviewRowSpacing;
|
|
|
|
// Widgets
|
|
public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
|
|
|
|
// Drop Target
|
|
public int dropTargetBarSizePx;
|
|
public int dropTargetDragPaddingPx;
|
|
public int dropTargetTextSizePx;
|
|
|
|
// Insets
|
|
private final Rect mInsets = new Rect();
|
|
public final Rect workspacePadding = new Rect();
|
|
private final Rect mHotseatPadding = new Rect();
|
|
// When true, nav bar is on the left side of the screen.
|
|
private boolean mIsSeascape;
|
|
|
|
// Notification dots
|
|
public DotRenderer mDotRendererWorkSpace;
|
|
public DotRenderer mDotRendererAllApps;
|
|
|
|
// Taskbar
|
|
public boolean isTaskbarPresent;
|
|
// Whether Taskbar will inset the bottom of apps by taskbarSize.
|
|
public boolean isTaskbarPresentInApps;
|
|
public int taskbarSize;
|
|
// How much of the bottom inset is due to Taskbar rather than other system elements.
|
|
public int nonOverlappingTaskbarInset;
|
|
|
|
// DragController
|
|
public int flingToDeleteThresholdVelocity;
|
|
|
|
/** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */
|
|
DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
|
|
boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
|
|
boolean useTwoPanels) {
|
|
|
|
this.inv = inv;
|
|
this.isLandscape = windowBounds.isLandscape();
|
|
this.isMultiWindowMode = isMultiWindowMode;
|
|
this.transposeLayoutWithOrientation = transposeLayoutWithOrientation;
|
|
windowX = windowBounds.bounds.left;
|
|
windowY = windowBounds.bounds.top;
|
|
|
|
isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
|
|
|
|
// Determine sizes.
|
|
widthPx = windowBounds.bounds.width();
|
|
heightPx = windowBounds.bounds.height();
|
|
availableWidthPx = windowBounds.availableSize.x;
|
|
int nonFinalAvailableHeightPx = windowBounds.availableSize.y;
|
|
|
|
mInfo = info;
|
|
// If the device's pixel density was scaled (usually via settings for A11y), use the
|
|
// original dimensions to determine if rotation is allowed of not.
|
|
float originalSmallestWidth = dpiFromPx(Math.min(widthPx, heightPx), DENSITY_DEVICE_STABLE);
|
|
allowRotation = originalSmallestWidth >= MIN_TABLET_WIDTH;
|
|
// Tablet UI does not support emulated landscape.
|
|
isTablet = allowRotation && info.isTablet(windowBounds);
|
|
isPhone = !isTablet;
|
|
isTwoPanels = isTablet && useTwoPanels
|
|
&& (isLandscape || FeatureFlags.ENABLE_TWO_PANEL_HOME_IN_PORTRAIT.get());
|
|
|
|
aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
|
|
boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
|
|
|
|
// Some more constants
|
|
context = getContext(context, info, isVerticalBarLayout()
|
|
? Configuration.ORIENTATION_LANDSCAPE
|
|
: Configuration.ORIENTATION_PORTRAIT);
|
|
mMetrics = context.getResources().getDisplayMetrics();
|
|
final Resources res = context.getResources();
|
|
|
|
hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height);
|
|
isTaskbarPresent = isTablet && ApiWrapper.TASKBAR_DRAWN_IN_PROCESS
|
|
&& FeatureFlags.ENABLE_TASKBAR.get();
|
|
if (isTaskbarPresent) {
|
|
// Taskbar will be added later, but provides bottom insets that we should subtract
|
|
// from availableHeightPx.
|
|
taskbarSize = res.getDimensionPixelSize(R.dimen.taskbar_size);
|
|
nonOverlappingTaskbarInset = taskbarSize - windowBounds.insets.bottom;
|
|
if (nonOverlappingTaskbarInset > 0) {
|
|
nonFinalAvailableHeightPx -= nonOverlappingTaskbarInset;
|
|
}
|
|
}
|
|
availableHeightPx = nonFinalAvailableHeightPx;
|
|
|
|
edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
|
|
|
|
desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : isScalableGrid
|
|
? res.getDimensionPixelSize(R.dimen.scalable_grid_left_right_margin)
|
|
: res.getDimensionPixelSize(R.dimen.dynamic_grid_left_right_margin);
|
|
desiredWorkspaceLeftRightOriginalPx = desiredWorkspaceLeftRightMarginPx;
|
|
|
|
|
|
allAppsOpenVerticalTranslate = res.getDimensionPixelSize(
|
|
R.dimen.all_apps_open_vertical_translate);
|
|
|
|
folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
|
|
folderContentPaddingLeftRight =
|
|
res.getDimensionPixelSize(R.dimen.folder_content_padding_left_right);
|
|
folderContentPaddingTop = res.getDimensionPixelSize(R.dimen.folder_content_padding_top);
|
|
|
|
setCellLayoutBorderSpacing(pxFromDp(inv.borderSpacing, mMetrics, 1f));
|
|
cellLayoutBorderSpacingOriginalPx = cellLayoutBorderSpacingPx;
|
|
folderCellLayoutBorderSpacingPx = cellLayoutBorderSpacingPx;
|
|
|
|
int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
|
|
? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
|
|
int cellLayoutPadding = isScalableGrid
|
|
? 0
|
|
: res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
|
|
|
|
if (isTwoPanels) {
|
|
cellLayoutPaddingLeftRightPx = res.getDimensionPixelSize(
|
|
isLandscape
|
|
? R.dimen.two_panels_home_side_padding_landscape
|
|
: R.dimen.two_panels_home_side_padding_portrait);
|
|
cellLayoutBottomPaddingPx = 0;
|
|
} else if (isLandscape) {
|
|
cellLayoutPaddingLeftRightPx = 0;
|
|
cellLayoutBottomPaddingPx = cellLayoutPadding;
|
|
} else {
|
|
cellLayoutPaddingLeftRightPx = cellLayoutPaddingLeftRightMultiplier * cellLayoutPadding;
|
|
cellLayoutBottomPaddingPx = 0;
|
|
}
|
|
|
|
workspacePageIndicatorHeight = res.getDimensionPixelSize(
|
|
R.dimen.workspace_page_indicator_height);
|
|
mWorkspacePageIndicatorOverlapWorkspace =
|
|
res.getDimensionPixelSize(R.dimen.workspace_page_indicator_overlap_workspace);
|
|
|
|
iconDrawablePaddingOriginalPx =
|
|
res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
|
|
|
|
dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
|
|
dropTargetDragPaddingPx = res.getDimensionPixelSize(R.dimen.drop_target_drag_padding);
|
|
dropTargetTextSizePx = res.getDimensionPixelSize(R.dimen.drop_target_text_size);
|
|
|
|
workspaceSpringLoadedBottomSpace =
|
|
res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
|
|
|
|
workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
|
|
|
|
numShownHotseatIcons =
|
|
isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons;
|
|
numShownAllAppsColumns =
|
|
isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns;
|
|
hotseatBarSizeExtraSpacePx = 0;
|
|
hotseatBarTopPaddingPx =
|
|
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
|
|
hotseatBarBottomPaddingPx = (isTallDevice ? 0
|
|
: res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding))
|
|
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
|
|
hotseatBarSidePaddingEndPx =
|
|
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
|
|
// Add a bit of space between nav bar and hotseat in vertical bar layout.
|
|
hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
|
|
hotseatExtraVerticalSize =
|
|
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size);
|
|
updateHotseatIconSize(pxFromDp(inv.iconSize, mMetrics, 1f));
|
|
|
|
qsbBottomMarginOriginalPx = isScalableGrid
|
|
? res.getDimensionPixelSize(R.dimen.scalable_grid_qsb_bottom_margin)
|
|
: 0;
|
|
|
|
overviewShowAsGrid = isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
|
|
overviewTaskMarginPx = overviewShowAsGrid
|
|
? res.getDimensionPixelSize(R.dimen.overview_task_margin_focused)
|
|
: res.getDimensionPixelSize(R.dimen.overview_task_margin);
|
|
overviewTaskMarginGridPx = res.getDimensionPixelSize(R.dimen.overview_task_margin_grid);
|
|
overviewTaskIconSizePx = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
|
|
overviewTaskIconDrawableSizePx =
|
|
res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size);
|
|
overviewTaskIconDrawableSizeGridPx =
|
|
res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid);
|
|
overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx * 2;
|
|
if (overviewShowAsGrid) {
|
|
if (isLandscape) {
|
|
overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
|
|
R.dimen.overview_actions_top_margin_gesture_grid_landscape);
|
|
overviewActionsBottomMarginGesturePx = res.getDimensionPixelSize(
|
|
R.dimen.overview_actions_bottom_margin_gesture_grid_landscape);
|
|
} else {
|
|
overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
|
|
R.dimen.overview_actions_top_margin_gesture_grid_portrait);
|
|
overviewActionsBottomMarginGesturePx = res.getDimensionPixelSize(
|
|
R.dimen.overview_actions_bottom_margin_gesture_grid_portrait);
|
|
}
|
|
} else {
|
|
overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
|
|
R.dimen.overview_actions_margin_gesture);
|
|
overviewActionsBottomMarginGesturePx = overviewActionsTopMarginGesturePx;
|
|
}
|
|
overviewActionsMarginThreeButtonPx = res.getDimensionPixelSize(
|
|
R.dimen.overview_actions_margin_three_button);
|
|
overviewPageSpacing = overviewShowAsGrid
|
|
? res.getDimensionPixelSize(R.dimen.recents_page_spacing_grid)
|
|
: res.getDimensionPixelSize(R.dimen.recents_page_spacing);
|
|
overviewRowSpacing = isLandscape
|
|
? res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing_landscape)
|
|
: res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing_portrait);
|
|
|
|
// Calculate all of the remaining variables.
|
|
extraSpace = updateAvailableDimensions(res);
|
|
|
|
// Now that we have all of the variables calculated, we can tune certain sizes.
|
|
if (isScalableGrid && inv.devicePaddings != null) {
|
|
// Paddings were created assuming no scaling, so we first unscale the extra space.
|
|
int unscaledExtraSpace = (int) (extraSpace / cellScaleToFit);
|
|
DevicePadding padding = inv.devicePaddings.getDevicePadding(unscaledExtraSpace);
|
|
|
|
int paddingWorkspaceTop = padding.getWorkspaceTopPadding(unscaledExtraSpace);
|
|
int paddingWorkspaceBottom = padding.getWorkspaceBottomPadding(unscaledExtraSpace);
|
|
int paddingHotseatBottom = padding.getHotseatBottomPadding(unscaledExtraSpace);
|
|
|
|
workspaceTopPadding = Math.round(paddingWorkspaceTop * cellScaleToFit);
|
|
workspaceBottomPadding = Math.round(paddingWorkspaceBottom * cellScaleToFit);
|
|
extraHotseatBottomPadding = Math.round(paddingHotseatBottom * cellScaleToFit);
|
|
|
|
hotseatBarSizePx += extraHotseatBottomPadding;
|
|
|
|
qsbBottomMarginPx = Math.round(qsbBottomMarginOriginalPx * cellScaleToFit);
|
|
} else if (!isVerticalBarLayout() && isPhone && isTallDevice) {
|
|
// We increase the hotseat size when there is extra space.
|
|
|
|
if (Float.compare(aspectRatio, TALLER_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0
|
|
&& extraSpace >= Utilities.dpToPx(TALL_DEVICE_EXTRA_SPACE_THRESHOLD_DP)) {
|
|
// For taller devices, we will take a piece of the extra space from each row,
|
|
// and add it to the space above and below the hotseat.
|
|
|
|
// For devices with more extra space, we take a larger piece from each cell.
|
|
int piece = extraSpace < Utilities.dpToPx(TALL_DEVICE_MORE_EXTRA_SPACE_THRESHOLD_DP)
|
|
? 7 : 5;
|
|
|
|
int extraSpace = ((getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2)
|
|
* inv.numRows) / piece;
|
|
|
|
workspaceTopPadding = extraSpace / 8;
|
|
int halfLeftOver = (extraSpace - workspaceTopPadding) / 2;
|
|
hotseatBarTopPaddingPx += halfLeftOver;
|
|
hotseatBarSizeExtraSpacePx = halfLeftOver;
|
|
} else {
|
|
// ie. For a display with a large aspect ratio, we can keep the icons on the
|
|
// workspace in portrait mode closer together by adding more height to the hotseat.
|
|
// Note: This calculation was created after noticing a pattern in the design spec.
|
|
hotseatBarSizeExtraSpacePx = getCellSize().y - iconSizePx
|
|
- iconDrawablePaddingPx * 2 - workspacePageIndicatorHeight;
|
|
}
|
|
|
|
updateHotseatIconSize(iconSizePx);
|
|
|
|
// Recalculate the available dimensions using the new hotseat size.
|
|
updateAvailableDimensions(res);
|
|
}
|
|
updateWorkspacePadding();
|
|
|
|
flingToDeleteThresholdVelocity = res.getDimensionPixelSize(
|
|
R.dimen.drag_flingToDeleteMinVelocity);
|
|
|
|
// This is done last, after iconSizePx is calculated above.
|
|
Path dotPath = GraphicsUtils.getShapePath(DEFAULT_DOT_SIZE);
|
|
mDotRendererWorkSpace = new DotRenderer(iconSizePx, dotPath, DEFAULT_DOT_SIZE);
|
|
mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace :
|
|
new DotRenderer(allAppsIconSizePx, dotPath, DEFAULT_DOT_SIZE);
|
|
}
|
|
|
|
private void updateHotseatIconSize(int hotseatIconSizePx) {
|
|
// Ensure there is enough space for folder icons, which have a slightly larger radius.
|
|
hotseatCellHeightPx = (int) Math.ceil(hotseatIconSizePx * ICON_OVERLAP_FACTOR);
|
|
if (isVerticalBarLayout()) {
|
|
hotseatBarSizePx = hotseatIconSizePx + hotseatBarSidePaddingStartPx
|
|
+ hotseatBarSidePaddingEndPx;
|
|
} else {
|
|
hotseatBarSizePx = hotseatIconSizePx + hotseatBarTopPaddingPx
|
|
+ hotseatBarBottomPaddingPx + (isScalableGrid ? 0 : hotseatExtraVerticalSize)
|
|
+ hotseatBarSizeExtraSpacePx;
|
|
}
|
|
}
|
|
|
|
private void setCellLayoutBorderSpacing(int borderSpacing) {
|
|
cellLayoutBorderSpacingPx = isScalableGrid ? borderSpacing : 0;
|
|
}
|
|
|
|
public Info getDisplayInfo() {
|
|
return mInfo;
|
|
}
|
|
|
|
/**
|
|
* We inset the widget padding added by the system and instead rely on the border spacing
|
|
* between cells to create reliable consistency between widgets
|
|
*/
|
|
public boolean shouldInsetWidgets() {
|
|
Rect widgetPadding = inv.defaultWidgetPadding;
|
|
|
|
// Check all sides to ensure that the widget won't overlap into another cell, or into
|
|
// status bar.
|
|
return workspaceTopPadding > widgetPadding.top
|
|
&& cellLayoutBorderSpacingPx > widgetPadding.left
|
|
&& cellLayoutBorderSpacingPx > widgetPadding.top
|
|
&& cellLayoutBorderSpacingPx > widgetPadding.right
|
|
&& cellLayoutBorderSpacingPx > widgetPadding.bottom;
|
|
}
|
|
|
|
public Builder toBuilder(Context context) {
|
|
WindowBounds bounds =
|
|
new WindowBounds(widthPx, heightPx, availableWidthPx, availableHeightPx);
|
|
bounds.bounds.offsetTo(windowX, windowY);
|
|
return new Builder(context, inv, mInfo)
|
|
.setWindowBounds(bounds)
|
|
.setUseTwoPanels(isTwoPanels)
|
|
.setMultiWindowMode(isMultiWindowMode);
|
|
}
|
|
|
|
public DeviceProfile copy(Context context) {
|
|
return toBuilder(context).build();
|
|
}
|
|
|
|
/**
|
|
* TODO: Move this to the builder as part of setMultiWindowMode
|
|
*/
|
|
public DeviceProfile getMultiWindowProfile(Context context, WindowBounds windowBounds) {
|
|
DeviceProfile profile = toBuilder(context)
|
|
.setWindowBounds(windowBounds)
|
|
.setMultiWindowMode(true)
|
|
.build();
|
|
|
|
profile.hideWorkspaceLabelsIfNotEnoughSpace();
|
|
|
|
// We use these scales to measure and layout the widgets using their full invariant profile
|
|
// sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
|
|
float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
|
|
float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
|
|
profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
|
|
profile.updateWorkspacePadding();
|
|
|
|
return profile;
|
|
}
|
|
|
|
/**
|
|
* Checks if there is enough space for labels on the workspace.
|
|
* If there is not, labels on the Workspace are hidden.
|
|
* It is important to call this method after the All Apps variables have been set.
|
|
*/
|
|
private void hideWorkspaceLabelsIfNotEnoughSpace() {
|
|
float iconTextHeight = Utilities.calculateTextHeight(iconTextSizePx);
|
|
float workspaceCellPaddingY = getCellSize().y - iconSizePx - iconDrawablePaddingPx
|
|
- iconTextHeight;
|
|
|
|
// We want enough space so that the text is closer to its corresponding icon.
|
|
if (workspaceCellPaddingY < iconTextHeight) {
|
|
iconTextSizePx = 0;
|
|
iconDrawablePaddingPx = 0;
|
|
cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR);
|
|
autoResizeAllAppsCells();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Re-computes the all-apps cell size to be independent of workspace
|
|
*/
|
|
public void autoResizeAllAppsCells() {
|
|
int textHeight = Utilities.calculateTextHeight(allAppsIconTextSizePx);
|
|
int topBottomPadding = textHeight;
|
|
allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
|
|
+ textHeight + (topBottomPadding * 2);
|
|
}
|
|
|
|
/**
|
|
* Returns the amount of extra (or unused) vertical space.
|
|
*/
|
|
private int updateAvailableDimensions(Resources res) {
|
|
updateIconSize(1f, res);
|
|
|
|
Point workspacePadding = getTotalWorkspacePadding();
|
|
|
|
// Check to see if the icons fit within the available height.
|
|
float usedHeight = getCellLayoutHeight();
|
|
final int maxHeight = availableHeightPx - workspacePadding.y;
|
|
float extraHeight = Math.max(0, maxHeight - usedHeight);
|
|
float scaleY = maxHeight / usedHeight;
|
|
boolean shouldScale = scaleY < 1f;
|
|
|
|
float scaleX = 1f;
|
|
if (isScalableGrid) {
|
|
// We scale to fit the cellWidth and cellHeight in the available space.
|
|
// The benefit of scalable grids is that we can get consistent aspect ratios between
|
|
// devices.
|
|
int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
|
|
float usedWidth = (cellWidthPx * numColumns)
|
|
+ (cellLayoutBorderSpacingPx * (numColumns - 1))
|
|
+ (desiredWorkspaceLeftRightMarginPx * 2);
|
|
// We do not subtract padding here, as we also scale the workspace padding if needed.
|
|
scaleX = availableWidthPx / usedWidth;
|
|
shouldScale = true;
|
|
}
|
|
|
|
if (shouldScale) {
|
|
float scale = Math.min(scaleX, scaleY);
|
|
updateIconSize(scale, res);
|
|
extraHeight = Math.max(0, maxHeight - getCellLayoutHeight());
|
|
}
|
|
|
|
updateAvailableFolderCellDimensions(res);
|
|
return Math.round(extraHeight);
|
|
}
|
|
|
|
private int getCellLayoutHeight() {
|
|
return (cellHeightPx * inv.numRows) + (cellLayoutBorderSpacingPx * (inv.numRows - 1));
|
|
}
|
|
|
|
/**
|
|
* Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx,
|
|
* iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
|
|
* hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
|
|
*/
|
|
public void updateIconSize(float scale, Resources res) {
|
|
// Icon scale should never exceed 1, otherwise pixellation may occur.
|
|
iconScale = Math.min(1f, scale);
|
|
cellScaleToFit = scale;
|
|
|
|
// Workspace
|
|
final boolean isVerticalLayout = isVerticalBarLayout();
|
|
float invIconSizeDp;
|
|
float invIconTextSizeSp;
|
|
|
|
if (isTwoPanels) {
|
|
if (isLandscape) {
|
|
invIconSizeDp = inv.twoPanelLandscapeIconSize;
|
|
invIconTextSizeSp = inv.twoPanelLandscapeIconTextSize;
|
|
} else {
|
|
invIconSizeDp = inv.twoPanelPortraitIconSize;
|
|
invIconTextSizeSp = inv.twoPanelPortraitIconTextSize;
|
|
}
|
|
} else {
|
|
if (isLandscape) {
|
|
invIconSizeDp = inv.landscapeIconSize;
|
|
invIconTextSizeSp = inv.landscapeIconTextSize;
|
|
} else {
|
|
invIconSizeDp = inv.iconSize;
|
|
invIconTextSizeSp = inv.iconTextSize;
|
|
}
|
|
}
|
|
|
|
iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, iconScale));
|
|
iconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * iconScale);
|
|
iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * iconScale);
|
|
|
|
setCellLayoutBorderSpacing((int) (cellLayoutBorderSpacingOriginalPx * scale));
|
|
|
|
if (isScalableGrid) {
|
|
PointF minCellHeightAndWidth = getMinCellHeightAndWidth();
|
|
cellWidthPx = pxFromDp(minCellHeightAndWidth.x, mMetrics, scale);
|
|
cellHeightPx = pxFromDp(minCellHeightAndWidth.y, mMetrics, scale);
|
|
int cellContentHeight = iconSizePx + iconDrawablePaddingPx
|
|
+ Utilities.calculateTextHeight(iconTextSizePx);
|
|
cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
|
|
desiredWorkspaceLeftRightMarginPx = (int) (desiredWorkspaceLeftRightOriginalPx * scale);
|
|
} else {
|
|
cellWidthPx = iconSizePx + iconDrawablePaddingPx;
|
|
cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR)
|
|
+ iconDrawablePaddingPx
|
|
+ Utilities.calculateTextHeight(iconTextSizePx);
|
|
int cellPaddingY = (getCellSize().y - cellHeightPx) / 2;
|
|
if (iconDrawablePaddingPx > cellPaddingY && !isVerticalLayout
|
|
&& !isMultiWindowMode) {
|
|
// Ensures that the label is closer to its corresponding icon. This is not an issue
|
|
// with vertical bar layout or multi-window mode since the issue is handled
|
|
// separately with their calls to {@link #adjustToHideWorkspaceLabels}.
|
|
cellHeightPx -= (iconDrawablePaddingPx - cellPaddingY);
|
|
iconDrawablePaddingPx = cellPaddingY;
|
|
}
|
|
}
|
|
|
|
// All apps
|
|
if (numShownAllAppsColumns != inv.numColumns) {
|
|
allAppsIconSizePx = pxFromDp(inv.allAppsIconSize, mMetrics);
|
|
allAppsIconTextSizePx = pxFromSp(inv.allAppsIconTextSize, mMetrics);
|
|
allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
|
|
autoResizeAllAppsCells();
|
|
} else {
|
|
allAppsIconSizePx = iconSizePx;
|
|
allAppsIconTextSizePx = iconTextSizePx;
|
|
allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
|
|
allAppsCellHeightPx = getCellSize().y;
|
|
}
|
|
allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
|
|
|
|
if (isVerticalLayout) {
|
|
hideWorkspaceLabelsIfNotEnoughSpace();
|
|
}
|
|
|
|
// Hotseat
|
|
updateHotseatIconSize(iconSizePx);
|
|
|
|
if (!isVerticalLayout) {
|
|
int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx
|
|
- workspacePageIndicatorHeight - edgeMarginPx;
|
|
float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
|
|
workspaceSpringLoadShrinkFactor = Math.min(
|
|
res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f,
|
|
1 - (minRequiredHeight / expectedWorkspaceHeight));
|
|
} else {
|
|
workspaceSpringLoadShrinkFactor =
|
|
res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
|
|
}
|
|
|
|
// Folder icon
|
|
folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
|
|
folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
|
|
}
|
|
|
|
/**
|
|
* Returns the minimum cell height and width as a pair.
|
|
*/
|
|
private PointF getMinCellHeightAndWidth() {
|
|
PointF result = new PointF();
|
|
|
|
if (isTwoPanels) {
|
|
if (isLandscape) {
|
|
result.x = inv.twoPanelLandscapeMinCellWidthDps;
|
|
result.y = inv.twoPanelLandscapeMinCellHeightDps;
|
|
} else {
|
|
result.x = inv.twoPanelPortraitMinCellWidthDps;
|
|
result.y = inv.twoPanelPortraitMinCellHeightDps;
|
|
}
|
|
} else {
|
|
result.x = inv.minCellWidth;
|
|
result.y = inv.minCellHeight;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private void updateAvailableFolderCellDimensions(Resources res) {
|
|
updateFolderCellSize(1f, res);
|
|
|
|
final int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_height);
|
|
|
|
// Don't let the folder get too close to the edges of the screen.
|
|
int folderMargin = edgeMarginPx * 2;
|
|
Point totalWorkspacePadding = getTotalWorkspacePadding();
|
|
|
|
// Check if the icons fit within the available height.
|
|
float contentUsedHeight = folderCellHeightPx * inv.numFolderRows
|
|
+ ((inv.numFolderRows - 1) * folderCellLayoutBorderSpacingPx);
|
|
int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize
|
|
- folderMargin - folderContentPaddingTop;
|
|
float scaleY = contentMaxHeight / contentUsedHeight;
|
|
|
|
// Check if the icons fit within the available width.
|
|
float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns
|
|
+ ((inv.numFolderColumns - 1) * folderCellLayoutBorderSpacingPx);
|
|
int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin
|
|
- folderContentPaddingLeftRight * 2;
|
|
float scaleX = contentMaxWidth / contentUsedWidth;
|
|
|
|
float scale = Math.min(scaleX, scaleY);
|
|
if (scale < 1f) {
|
|
updateFolderCellSize(scale, res);
|
|
}
|
|
}
|
|
|
|
private void updateFolderCellSize(float scale, Resources res) {
|
|
float invIconSizeDp = isVerticalBarLayout() ? inv.landscapeIconSize : inv.iconSize;
|
|
folderChildIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale));
|
|
folderChildTextSizePx = pxFromSp(inv.iconTextSize, mMetrics, scale);
|
|
folderLabelTextSizePx = (int) (folderChildTextSizePx * folderLabelTextScale);
|
|
|
|
int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
|
|
|
|
if (isScalableGrid) {
|
|
int minWidth = folderChildIconSizePx + iconDrawablePaddingPx * 2;
|
|
int minHeight = folderChildIconSizePx + iconDrawablePaddingPx * 2 + textHeight;
|
|
|
|
folderCellWidthPx = (int) Math.max(minWidth, cellWidthPx * scale);
|
|
folderCellHeightPx = (int) Math.max(minHeight, cellHeightPx * scale);
|
|
|
|
int borderSpacing = (int) (cellLayoutBorderSpacingOriginalPx * scale);
|
|
folderCellLayoutBorderSpacingPx = borderSpacing;
|
|
folderContentPaddingLeftRight = borderSpacing;
|
|
folderContentPaddingTop = borderSpacing;
|
|
} else {
|
|
int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding)
|
|
* scale);
|
|
int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding)
|
|
* scale);
|
|
|
|
folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
|
|
folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
|
|
}
|
|
|
|
folderChildDrawablePaddingPx = Math.max(0,
|
|
(folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
|
|
}
|
|
|
|
public void updateInsets(Rect insets) {
|
|
mInsets.set(insets);
|
|
updateWorkspacePadding();
|
|
}
|
|
|
|
/**
|
|
* The current device insets. This is generally same as the insets being dispatched to
|
|
* {@link Insettable} elements, but can differ if the element is using a different profile.
|
|
*/
|
|
public Rect getInsets() {
|
|
return mInsets;
|
|
}
|
|
|
|
public Point getCellSize() {
|
|
return getCellSize(null);
|
|
}
|
|
|
|
public Point getCellSize(Point result) {
|
|
if (result == null) {
|
|
result = new Point();
|
|
}
|
|
|
|
// Since we are only concerned with the overall padding, layout direction does
|
|
// not matter.
|
|
Point padding = getTotalWorkspacePadding();
|
|
|
|
int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
|
|
int screenWidthPx = availableWidthPx - padding.x;
|
|
result.x = calculateCellWidth(screenWidthPx, cellLayoutBorderSpacingPx, numColumns);
|
|
result.y = calculateCellHeight(availableHeightPx - padding.y
|
|
- cellLayoutBottomPaddingPx, cellLayoutBorderSpacingPx, inv.numRows);
|
|
return result;
|
|
}
|
|
|
|
public Point getTotalWorkspacePadding() {
|
|
updateWorkspacePadding();
|
|
return new Point(workspacePadding.left + workspacePadding.right,
|
|
workspacePadding.top + workspacePadding.bottom);
|
|
}
|
|
|
|
/**
|
|
* Updates {@link #workspacePadding} as a result of any internal value change to reflect the
|
|
* new workspace padding
|
|
*/
|
|
private void updateWorkspacePadding() {
|
|
Rect padding = workspacePadding;
|
|
if (isVerticalBarLayout()) {
|
|
padding.top = 0;
|
|
padding.bottom = edgeMarginPx;
|
|
if (isSeascape()) {
|
|
padding.left = hotseatBarSizePx;
|
|
padding.right = hotseatBarSidePaddingStartPx;
|
|
} else {
|
|
padding.left = hotseatBarSidePaddingStartPx;
|
|
padding.right = hotseatBarSizePx;
|
|
}
|
|
} else {
|
|
int hotseatTop = isTaskbarPresent ? taskbarSize : hotseatBarSizePx;
|
|
int paddingBottom = hotseatTop + workspacePageIndicatorHeight
|
|
+ workspaceBottomPadding - mWorkspacePageIndicatorOverlapWorkspace;
|
|
if (isTablet) {
|
|
// Pad the left and right of the workspace to ensure consistent spacing
|
|
// between all icons
|
|
// The amount of screen space available for left/right padding.
|
|
int availablePaddingX = Math.max(0, widthPx - ((inv.numColumns * cellWidthPx) +
|
|
((inv.numColumns - 1) * cellWidthPx)));
|
|
availablePaddingX = (int) Math.min(availablePaddingX,
|
|
widthPx * MAX_HORIZONTAL_PADDING_PERCENT);
|
|
int hotseatVerticalPadding = isTaskbarPresent ? 0
|
|
: hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx;
|
|
int availablePaddingY = Math.max(0, heightPx - edgeMarginPx - paddingBottom
|
|
- (2 * inv.numRows * cellHeightPx) - hotseatVerticalPadding);
|
|
padding.set(availablePaddingX / 2, edgeMarginPx + availablePaddingY / 2,
|
|
availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
|
|
|
|
if (isTwoPanels) {
|
|
padding.set(0, padding.top, 0, padding.bottom);
|
|
}
|
|
} else {
|
|
// Pad the top and bottom of the workspace with search/hotseat bar sizes
|
|
padding.set(desiredWorkspaceLeftRightMarginPx,
|
|
workspaceTopPadding + (isScalableGrid ? 0 : edgeMarginPx),
|
|
desiredWorkspaceLeftRightMarginPx,
|
|
paddingBottom);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the padding for hotseat view
|
|
*/
|
|
public Rect getHotseatLayoutPadding(Context context) {
|
|
if (isVerticalBarLayout()) {
|
|
if (isSeascape()) {
|
|
mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx,
|
|
mInsets.top, hotseatBarSidePaddingEndPx, mInsets.bottom);
|
|
} else {
|
|
mHotseatPadding.set(hotseatBarSidePaddingEndPx, mInsets.top,
|
|
mInsets.right + hotseatBarSidePaddingStartPx, mInsets.bottom);
|
|
}
|
|
} else if (isTaskbarPresent) {
|
|
int hotseatHeight = workspacePadding.bottom + taskbarSize;
|
|
int taskbarOffset = getTaskbarOffsetY();
|
|
int hotseatTopDiff = hotseatHeight - taskbarSize - taskbarOffset;
|
|
|
|
int endOffset = ApiWrapper.getHotseatEndOffset(context);
|
|
int requiredWidth = iconSizePx * numShownHotseatIcons;
|
|
|
|
Resources res = context.getResources();
|
|
float taskbarIconSize = res.getDimension(R.dimen.taskbar_icon_size);
|
|
float taskbarIconSpacing = 2 * res.getDimension(R.dimen.taskbar_icon_spacing);
|
|
int maxSize = (int) (requiredWidth
|
|
* (taskbarIconSize + taskbarIconSpacing) / taskbarIconSize);
|
|
int hotseatSize = Math.min(maxSize, availableWidthPx - endOffset);
|
|
int sideSpacing = (availableWidthPx - hotseatSize) / 2;
|
|
mHotseatPadding.set(sideSpacing, hotseatTopDiff, sideSpacing, taskbarOffset);
|
|
|
|
if (endOffset > sideSpacing) {
|
|
int diff = Utilities.isRtl(context.getResources())
|
|
? sideSpacing - endOffset
|
|
: endOffset - sideSpacing;
|
|
mHotseatPadding.left -= diff;
|
|
mHotseatPadding.right += diff;
|
|
}
|
|
} else {
|
|
// We want the edges of the hotseat to line up with the edges of the workspace, but the
|
|
// icons in the hotseat are a different size, and so don't line up perfectly. To account
|
|
// for this, we pad the left and right of the hotseat with half of the difference of a
|
|
// workspace cell vs a hotseat cell.
|
|
float workspaceCellWidth = (float) widthPx / inv.numColumns;
|
|
float hotseatCellWidth = (float) widthPx / numShownHotseatIcons;
|
|
int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
|
|
mHotseatPadding.set(
|
|
hotseatAdjustment + workspacePadding.left + cellLayoutPaddingLeftRightPx
|
|
+ mInsets.left,
|
|
hotseatBarTopPaddingPx,
|
|
hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx
|
|
+ mInsets.right,
|
|
hotseatBarSizePx - hotseatCellHeightPx - hotseatBarTopPaddingPx
|
|
+ cellLayoutBottomPaddingPx + mInsets.bottom);
|
|
}
|
|
return mHotseatPadding;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of pixels the QSB is translated from the bottom of the screen.
|
|
*/
|
|
public int getQsbOffsetY() {
|
|
int freeSpace = isTaskbarPresent
|
|
? workspacePadding.bottom
|
|
: hotseatBarSizePx - hotseatCellHeightPx - hotseatQsbHeight;
|
|
|
|
if (isScalableGrid && qsbBottomMarginPx > mInsets.bottom) {
|
|
return Math.min(qsbBottomMarginPx, freeSpace);
|
|
} else {
|
|
return (int) (freeSpace * QSB_CENTER_FACTOR)
|
|
+ (isTaskbarPresent ? taskbarSize : mInsets.bottom);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the number of pixels the taskbar is translated from the bottom of the screen.
|
|
*/
|
|
public int getTaskbarOffsetY() {
|
|
return (getQsbOffsetY() - taskbarSize) / 2;
|
|
}
|
|
|
|
/**
|
|
* @return the bounds for which the open folders should be contained within
|
|
*/
|
|
public Rect getAbsoluteOpenFolderBounds() {
|
|
if (isVerticalBarLayout()) {
|
|
// Folders should only appear right of the drop target bar and left of the hotseat
|
|
return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx,
|
|
mInsets.top,
|
|
mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx,
|
|
mInsets.top + availableHeightPx);
|
|
} else {
|
|
// Folders should only appear below the drop target bar and above the hotseat
|
|
int hotseatTop = isTaskbarPresent ? taskbarSize : hotseatBarSizePx;
|
|
return new Rect(mInsets.left + edgeMarginPx,
|
|
mInsets.top + dropTargetBarSizePx + edgeMarginPx,
|
|
mInsets.left + availableWidthPx - edgeMarginPx,
|
|
mInsets.top + availableHeightPx - hotseatTop
|
|
- workspacePageIndicatorHeight - edgeMarginPx);
|
|
}
|
|
}
|
|
|
|
public static int calculateCellWidth(int width, int borderSpacing, int countX) {
|
|
return (width - ((countX - 1) * borderSpacing)) / countX;
|
|
}
|
|
public static int calculateCellHeight(int height, int borderSpacing, int countY) {
|
|
return (height - ((countY - 1) * borderSpacing)) / countY;
|
|
}
|
|
|
|
/**
|
|
* When {@code true}, the device is in landscape mode and the hotseat is on the right column.
|
|
* When {@code false}, either device is in portrait mode or the device is in landscape mode and
|
|
* the hotseat is on the bottom row.
|
|
*/
|
|
public boolean isVerticalBarLayout() {
|
|
return isLandscape && transposeLayoutWithOrientation;
|
|
}
|
|
|
|
/**
|
|
* Updates orientation information and returns true if it has changed from the previous value.
|
|
*/
|
|
public boolean updateIsSeascape(Context context) {
|
|
if (isVerticalBarLayout()) {
|
|
boolean isSeascape = DisplayController.INSTANCE.get(context)
|
|
.getInfo().rotation == Surface.ROTATION_270;
|
|
if (mIsSeascape != isSeascape) {
|
|
mIsSeascape = isSeascape;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean isSeascape() {
|
|
return isVerticalBarLayout() && mIsSeascape;
|
|
}
|
|
|
|
public boolean shouldFadeAdjacentWorkspaceScreens() {
|
|
return isVerticalBarLayout();
|
|
}
|
|
|
|
public int getCellContentHeight(@ContainerType int containerType) {
|
|
switch (containerType) {
|
|
case CellLayout.WORKSPACE:
|
|
return cellHeightPx;
|
|
case CellLayout.FOLDER:
|
|
return folderCellHeightPx;
|
|
case CellLayout.HOTSEAT:
|
|
// The hotseat is the only container where the cell height is going to be
|
|
// different from the content within that cell.
|
|
return iconSizePx;
|
|
default:
|
|
// ??
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private String pxToDpStr(String name, float value) {
|
|
return "\t" + name + ": " + value + "px (" + dpiFromPx(value, mMetrics.densityDpi) + "dp)";
|
|
}
|
|
|
|
public void dump(String prefix, PrintWriter writer) {
|
|
writer.println(prefix + "DeviceProfile:");
|
|
writer.println(prefix + "\t1 dp = " + mMetrics.density + " px");
|
|
|
|
writer.println(prefix + "\tallowRotation:" + allowRotation);
|
|
writer.println(prefix + "\tisTablet:" + isTablet);
|
|
writer.println(prefix + "\tisPhone:" + isPhone);
|
|
writer.println(prefix + "\ttransposeLayoutWithOrientation:"
|
|
+ transposeLayoutWithOrientation);
|
|
|
|
writer.println(prefix + "\tisLandscape:" + isLandscape);
|
|
writer.println(prefix + "\tisMultiWindowMode:" + isMultiWindowMode);
|
|
writer.println(prefix + "\tisTwoPanels:" + isTwoPanels);
|
|
|
|
writer.println(prefix + pxToDpStr("windowX", windowX));
|
|
writer.println(prefix + pxToDpStr("windowY", windowY));
|
|
writer.println(prefix + pxToDpStr("widthPx", widthPx));
|
|
writer.println(prefix + pxToDpStr("heightPx", heightPx));
|
|
|
|
writer.println(prefix + pxToDpStr("availableWidthPx", availableWidthPx));
|
|
writer.println(prefix + pxToDpStr("availableHeightPx", availableHeightPx));
|
|
|
|
writer.println(prefix + "\taspectRatio:" + aspectRatio);
|
|
|
|
writer.println(prefix + "\tisScalableGrid:" + isScalableGrid);
|
|
|
|
writer.println(prefix + "\tinv.minCellWidth:" + inv.minCellWidth + "dp");
|
|
writer.println(prefix + "\tinv.minCellHeight:" + inv.minCellHeight + "dp");
|
|
writer.println(prefix + "\tinv.twoPanelPortraitMinCellHeightDps:"
|
|
+ inv.twoPanelPortraitMinCellHeightDps + "dp");
|
|
writer.println(prefix + "\tinv.twoPanelPortraitMinCellWidthDps:"
|
|
+ inv.twoPanelPortraitMinCellWidthDps + "dp");
|
|
writer.println(prefix + "\tinv.twoPanelLandscapeMinCellHeightDps:"
|
|
+ inv.twoPanelLandscapeMinCellHeightDps + "dp");
|
|
writer.println(prefix + "\tinv.twoPanelLandscapeMinCellWidthDps:"
|
|
+ inv.twoPanelLandscapeMinCellWidthDps + "dp");
|
|
|
|
writer.println(prefix + "\tinv.numColumns:" + inv.numColumns);
|
|
writer.println(prefix + "\tinv.numRows:" + inv.numRows);
|
|
|
|
writer.println(prefix + pxToDpStr("cellWidthPx", cellWidthPx));
|
|
writer.println(prefix + pxToDpStr("cellHeightPx", cellHeightPx));
|
|
|
|
writer.println(prefix + pxToDpStr("getCellSize().x", getCellSize().x));
|
|
writer.println(prefix + pxToDpStr("getCellSize().y", getCellSize().y));
|
|
|
|
writer.println(prefix + "\tinv.iconSize:" + inv.iconSize + "dp");
|
|
writer.println(prefix + pxToDpStr("iconSizePx", iconSizePx));
|
|
writer.println(prefix + pxToDpStr("iconTextSizePx", iconTextSizePx));
|
|
writer.println(prefix + pxToDpStr("iconDrawablePaddingPx", iconDrawablePaddingPx));
|
|
|
|
writer.println(prefix + pxToDpStr("folderCellWidthPx", folderCellWidthPx));
|
|
writer.println(prefix + pxToDpStr("folderCellHeightPx", folderCellHeightPx));
|
|
writer.println(prefix + pxToDpStr("folderChildIconSizePx", folderChildIconSizePx));
|
|
writer.println(prefix + pxToDpStr("folderChildTextSizePx", folderChildTextSizePx));
|
|
writer.println(prefix + pxToDpStr("folderChildDrawablePaddingPx",
|
|
folderChildDrawablePaddingPx));
|
|
writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacingPx",
|
|
folderCellLayoutBorderSpacingPx));
|
|
|
|
writer.println(prefix + pxToDpStr("cellLayoutBorderSpacingPx",
|
|
cellLayoutBorderSpacingPx));
|
|
writer.println(prefix + pxToDpStr("desiredWorkspaceLeftRightMarginPx",
|
|
desiredWorkspaceLeftRightMarginPx));
|
|
|
|
writer.println(prefix + pxToDpStr("allAppsIconSizePx", allAppsIconSizePx));
|
|
writer.println(prefix + pxToDpStr("allAppsIconTextSizePx", allAppsIconTextSizePx));
|
|
writer.println(prefix + pxToDpStr("allAppsIconDrawablePaddingPx",
|
|
allAppsIconDrawablePaddingPx));
|
|
writer.println(prefix + pxToDpStr("allAppsCellHeightPx", allAppsCellHeightPx));
|
|
writer.println(prefix + "\tnumShownAllAppsColumns: " + numShownAllAppsColumns);
|
|
|
|
writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx));
|
|
writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx));
|
|
writer.println(prefix + pxToDpStr("hotseatBarTopPaddingPx", hotseatBarTopPaddingPx));
|
|
writer.println(prefix + pxToDpStr("hotseatBarBottomPaddingPx", hotseatBarBottomPaddingPx));
|
|
writer.println(prefix + pxToDpStr("hotseatBarSidePaddingStartPx",
|
|
hotseatBarSidePaddingStartPx));
|
|
writer.println(prefix + pxToDpStr("hotseatBarSidePaddingEndPx",
|
|
hotseatBarSidePaddingEndPx));
|
|
writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons);
|
|
|
|
writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent);
|
|
writer.println(prefix + "\tisTaskbarPresentInApps:" + isTaskbarPresentInApps);
|
|
|
|
writer.println(prefix + pxToDpStr("taskbarSize", taskbarSize));
|
|
writer.println(prefix + pxToDpStr("nonOverlappingTaskbarInset",
|
|
nonOverlappingTaskbarInset));
|
|
|
|
writer.println(prefix + pxToDpStr("workspacePadding.left", workspacePadding.left));
|
|
writer.println(prefix + pxToDpStr("workspacePadding.top", workspacePadding.top));
|
|
writer.println(prefix + pxToDpStr("workspacePadding.right", workspacePadding.right));
|
|
writer.println(prefix + pxToDpStr("workspacePadding.bottom", workspacePadding.bottom));
|
|
|
|
writer.println(prefix + pxToDpStr("iconScale", iconScale));
|
|
writer.println(prefix + pxToDpStr("cellScaleToFit ", cellScaleToFit));
|
|
writer.println(prefix + pxToDpStr("extraSpace", extraSpace));
|
|
|
|
if (inv.devicePaddings != null) {
|
|
int unscaledExtraSpace = (int) (extraSpace / iconScale);
|
|
writer.println(prefix + pxToDpStr("maxEmptySpace",
|
|
inv.devicePaddings.getDevicePadding(unscaledExtraSpace).getMaxEmptySpacePx()));
|
|
}
|
|
writer.println(prefix + pxToDpStr("workspaceTopPadding", workspaceTopPadding));
|
|
writer.println(prefix + pxToDpStr("workspaceBottomPadding", workspaceBottomPadding));
|
|
writer.println(prefix + pxToDpStr("extraHotseatBottomPadding", extraHotseatBottomPadding));
|
|
}
|
|
|
|
private static Context getContext(Context c, Info info, int orientation) {
|
|
Configuration config = new Configuration(c.getResources().getConfiguration());
|
|
config.orientation = orientation;
|
|
config.densityDpi = info.densityDpi;
|
|
return c.createConfigurationContext(config);
|
|
}
|
|
|
|
/**
|
|
* Callback when a component changes the DeviceProfile associated with it, as a result of
|
|
* configuration change
|
|
*/
|
|
public interface OnDeviceProfileChangeListener {
|
|
|
|
/**
|
|
* Called when the device profile is reassigned. Note that for layout and measurements, it
|
|
* is sufficient to listen for inset changes. Use this callback when you need to perform
|
|
* a one time operation.
|
|
*/
|
|
void onDeviceProfileChanged(DeviceProfile dp);
|
|
}
|
|
|
|
public static class Builder {
|
|
private Context mContext;
|
|
private InvariantDeviceProfile mInv;
|
|
private Info mInfo;
|
|
|
|
private WindowBounds mWindowBounds;
|
|
private boolean mUseTwoPanels;
|
|
|
|
private boolean mIsMultiWindowMode = false;
|
|
private Boolean mTransposeLayoutWithOrientation;
|
|
|
|
public Builder(Context context, InvariantDeviceProfile inv, Info info) {
|
|
mContext = context;
|
|
mInv = inv;
|
|
mInfo = info;
|
|
}
|
|
|
|
public Builder setMultiWindowMode(boolean isMultiWindowMode) {
|
|
mIsMultiWindowMode = isMultiWindowMode;
|
|
return this;
|
|
}
|
|
|
|
public Builder setUseTwoPanels(boolean useTwoPanels) {
|
|
mUseTwoPanels = useTwoPanels;
|
|
return this;
|
|
}
|
|
|
|
|
|
public Builder setWindowBounds(WindowBounds bounds) {
|
|
mWindowBounds = bounds;
|
|
return this;
|
|
}
|
|
|
|
public Builder setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation) {
|
|
mTransposeLayoutWithOrientation = transposeLayoutWithOrientation;
|
|
return this;
|
|
}
|
|
|
|
public DeviceProfile build() {
|
|
if (mWindowBounds == null) {
|
|
throw new IllegalArgumentException("Window bounds not set");
|
|
}
|
|
if (mTransposeLayoutWithOrientation == null) {
|
|
mTransposeLayoutWithOrientation = !mInfo.isTablet(mWindowBounds);
|
|
}
|
|
return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds,
|
|
mIsMultiWindowMode, mTransposeLayoutWithOrientation, mUseTwoPanels);
|
|
}
|
|
}
|
|
|
|
}
|