| /* |
| * 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 com.android.launcher3.InvariantDeviceProfile.INDEX_DEFAULT; |
| import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE; |
| import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE; |
| import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT; |
| 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 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.view.Surface; |
| |
| import com.android.launcher3.CellLayout.ContainerType; |
| import com.android.launcher3.DevicePaddings.DevicePadding; |
| 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; |
| import java.util.List; |
| |
| @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 final float mQsbCenterFactor; |
| |
| 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 isQsbInline; |
| |
| // Device properties in current orientation |
| public final boolean isLandscape; |
| public final boolean isMultiWindowMode; |
| public final boolean isGestureMode; |
| |
| 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 int rotationHint; |
| |
| public final float aspectRatio; |
| |
| public final boolean isScalableGrid; |
| private final int mTypeIndex; |
| |
| /** |
| * 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; |
| |
| // Workspace |
| public final int desiredWorkspaceHorizontalMarginOriginalPx; |
| public int desiredWorkspaceHorizontalMarginPx; |
| public Point cellLayoutBorderSpaceOriginalPx; |
| public Point cellLayoutBorderSpacePx; |
| public Rect cellLayoutPaddingPx = new Rect(); |
| |
| public final int edgeMarginPx; |
| public float workspaceSpringLoadShrunkTop; |
| public float workspaceSpringLoadShrunkBottom; |
| 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 Point folderCellLayoutBorderSpacePx; |
| public int folderCellLayoutBorderSpaceOriginalPx; |
| 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; |
| public int springLoadedHotseatBarTopMarginPx; |
| // 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 int hotseatBorderSpace; |
| |
| public final float qsbBottomMarginOriginalPx; |
| public int qsbBottomMarginPx; |
| public int qsbWidth; // only used when isQsbInline |
| |
| // All apps |
| public Point allAppsBorderSpacePx; |
| public int allAppsShiftRange; |
| public int allAppsTopPadding; |
| public int bottomSheetTopPadding; |
| public int allAppsCellHeightPx; |
| public int allAppsCellWidthPx; |
| public int allAppsIconSizePx; |
| public int allAppsIconDrawablePaddingPx; |
| public int allAppsLeftRightPadding; |
| public int allAppsLeftRightMargin; |
| public final int numShownAllAppsColumns; |
| public float allAppsIconTextSizePx; |
| |
| // Overview |
| public int overviewTaskMarginPx; |
| public int overviewTaskMarginGridPx; |
| public int overviewTaskIconSizePx; |
| public int overviewTaskIconDrawableSizePx; |
| public int overviewTaskIconDrawableSizeGridPx; |
| public int overviewTaskThumbnailTopMarginPx; |
| public final int overviewActionsHeight; |
| public final int overviewActionsMarginThreeButtonPx; |
| public final int overviewActionsTopMarginGesturePx; |
| public final int overviewActionsBottomMarginGesturePx; |
| public final int overviewActionsButtonSpacing; |
| public int overviewPageSpacing; |
| public int overviewRowSpacing; |
| public int overviewGridSideMargin; |
| |
| // Widgets |
| public final PointF appWidgetScale = new PointF(1.0f, 1.0f); |
| |
| // Drop Target |
| public int dropTargetBarSizePx; |
| public int dropTargetBarTopMarginPx; |
| public int dropTargetBarBottomMarginPx; |
| public int dropTargetDragPaddingPx; |
| public int dropTargetTextSizePx; |
| public int dropTargetHorizontalPaddingPx; |
| public int dropTargetVerticalPaddingPx; |
| public int dropTargetGapPx; |
| |
| // 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; |
| public int stashedTaskbarSize; |
| |
| // 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, boolean isGestureMode) { |
| |
| this.inv = inv; |
| this.isLandscape = windowBounds.isLandscape(); |
| this.isMultiWindowMode = isMultiWindowMode; |
| this.transposeLayoutWithOrientation = transposeLayoutWithOrientation; |
| this.isGestureMode = isGestureMode; |
| windowX = windowBounds.bounds.left; |
| windowY = windowBounds.bounds.top; |
| this.rotationHint = windowBounds.rotationHint; |
| mInsets.set(windowBounds.insets); |
| |
| isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode; |
| |
| // Determine device posture. |
| mInfo = info; |
| isTablet = info.isTablet(windowBounds); |
| isPhone = !isTablet; |
| isTwoPanels = isTablet && useTwoPanels; |
| isTaskbarPresent = isTablet && ApiWrapper.TASKBAR_DRAWN_IN_PROCESS; |
| |
| // Some more constants. |
| context = getContext(context, info, isVerticalBarLayout() || (isTablet && isLandscape) |
| ? Configuration.ORIENTATION_LANDSCAPE |
| : Configuration.ORIENTATION_PORTRAIT, |
| windowBounds); |
| final Resources res = context.getResources(); |
| mMetrics = res.getDisplayMetrics(); |
| |
| // Determine sizes. |
| widthPx = windowBounds.bounds.width(); |
| heightPx = windowBounds.bounds.height(); |
| availableWidthPx = windowBounds.availableSize.x; |
| availableHeightPx = windowBounds.availableSize.y; |
| |
| aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx); |
| boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0; |
| mQsbCenterFactor = res.getFloat(R.dimen.qsb_center_factor); |
| |
| if (isTwoPanels) { |
| if (isLandscape) { |
| mTypeIndex = INDEX_TWO_PANEL_LANDSCAPE; |
| } else { |
| mTypeIndex = INDEX_TWO_PANEL_PORTRAIT; |
| } |
| } else { |
| if (isLandscape) { |
| mTypeIndex = INDEX_LANDSCAPE; |
| } else { |
| mTypeIndex = INDEX_DEFAULT; |
| } |
| } |
| |
| if (isTaskbarPresent) { |
| taskbarSize = res.getDimensionPixelSize(R.dimen.taskbar_size); |
| stashedTaskbarSize = res.getDimensionPixelSize(R.dimen.taskbar_stashed_size); |
| } |
| |
| edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin); |
| |
| desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res); |
| desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx; |
| |
| bottomSheetTopPadding = mInsets.top // statusbar height |
| + res.getDimensionPixelSize(R.dimen.bottom_sheet_extra_top_padding) |
| + (isTablet ? 0 : edgeMarginPx); // phones need edgeMarginPx additional padding |
| |
| allAppsTopPadding = isTablet ? bottomSheetTopPadding : 0; |
| allAppsShiftRange = isTablet |
| ? heightPx - allAppsTopPadding |
| : res.getDimensionPixelSize(R.dimen.all_apps_starting_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); |
| |
| cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv); |
| allAppsBorderSpacePx = new Point( |
| pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics), |
| pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics)); |
| cellLayoutBorderSpaceOriginalPx = new Point(cellLayoutBorderSpacePx); |
| folderCellLayoutBorderSpaceOriginalPx = pxFromDp(inv.folderBorderSpace, mMetrics); |
| folderCellLayoutBorderSpacePx = new Point(folderCellLayoutBorderSpaceOriginalPx, |
| folderCellLayoutBorderSpaceOriginalPx); |
| |
| 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); |
| dropTargetBarTopMarginPx = res.getDimensionPixelSize(R.dimen.drop_target_top_margin); |
| dropTargetBarBottomMarginPx = res.getDimensionPixelSize(R.dimen.drop_target_bottom_margin); |
| dropTargetDragPaddingPx = res.getDimensionPixelSize(R.dimen.drop_target_drag_padding); |
| dropTargetTextSizePx = res.getDimensionPixelSize(R.dimen.drop_target_text_size); |
| dropTargetHorizontalPaddingPx = res.getDimensionPixelSize( |
| R.dimen.drop_target_button_drawable_horizontal_padding); |
| dropTargetVerticalPaddingPx = res.getDimensionPixelSize( |
| R.dimen.drop_target_button_drawable_vertical_padding); |
| dropTargetGapPx = res.getDimensionPixelSize(R.dimen.drop_target_button_gap); |
| |
| workspaceSpringLoadedBottomSpace = |
| res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space); |
| |
| workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x); |
| |
| hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height); |
| // Whether QSB might be inline in appropriate orientation (e.g. landscape). |
| boolean canQsbInline = (isTwoPanels ? inv.inlineQsb[INDEX_TWO_PANEL_PORTRAIT] |
| || inv.inlineQsb[INDEX_TWO_PANEL_LANDSCAPE] |
| : inv.inlineQsb[INDEX_DEFAULT] || inv.inlineQsb[INDEX_LANDSCAPE]) |
| && hotseatQsbHeight > 0; |
| isQsbInline = inv.inlineQsb[mTypeIndex] && canQsbInline; |
| |
| // We shrink hotseat sizes regardless of orientation, if nav buttons are inline and QSB |
| // might be inline in either orientations, to keep hotseat size consistent across rotation. |
| boolean areNavButtonsInline = isTaskbarPresent && !isGestureMode; |
| if (areNavButtonsInline && canQsbInline) { |
| numShownHotseatIcons = inv.numShrunkenHotseatIcons; |
| } else { |
| numShownHotseatIcons = |
| isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons; |
| } |
| |
| numShownAllAppsColumns = |
| isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns; |
| hotseatBarSizeExtraSpacePx = 0; |
| hotseatBarTopPaddingPx = |
| res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding); |
| if (isQsbInline) { |
| hotseatBarBottomPaddingPx = res.getDimensionPixelSize(R.dimen.inline_qsb_bottom_margin); |
| qsbWidth = calculateQsbWidth(); |
| } else { |
| hotseatBarBottomPaddingPx = (isTallDevice ? res.getDimensionPixelSize( |
| R.dimen.dynamic_grid_hotseat_bottom_tall_padding) |
| : res.getDimensionPixelSize( |
| R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding)) |
| + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding); |
| qsbWidth = 0; |
| } |
| springLoadedHotseatBarTopMarginPx = res.getDimensionPixelSize( |
| R.dimen.spring_loaded_hotseat_top_margin); |
| 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); |
| hotseatBorderSpace = pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics); |
| updateHotseatIconSize( |
| pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics)); |
| |
| qsbBottomMarginOriginalPx = isScalableGrid |
| ? res.getDimensionPixelSize(R.dimen.scalable_grid_qsb_bottom_margin) |
| : 0; |
| |
| overviewTaskMarginPx = 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; |
| overviewActionsTopMarginGesturePx = res.getDimensionPixelSize( |
| R.dimen.overview_actions_top_margin_gesture); |
| overviewActionsBottomMarginGesturePx = res.getDimensionPixelSize( |
| R.dimen.overview_actions_bottom_margin_gesture); |
| overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing); |
| overviewActionsButtonSpacing = res.getDimensionPixelSize( |
| R.dimen.overview_actions_button_spacing); |
| overviewActionsHeight = res.getDimensionPixelSize(R.dimen.overview_actions_height); |
| overviewActionsMarginThreeButtonPx = res.getDimensionPixelSize( |
| R.dimen.overview_actions_margin_three_button); |
| // Grid task's top margin is only overviewTaskIconSizePx + overviewTaskMarginGridPx, but |
| // overviewTaskThumbnailTopMarginPx is applied to all TaskThumbnailView, so exclude the |
| // extra margin when calculating row spacing. |
| int extraTopMargin = overviewTaskThumbnailTopMarginPx - overviewTaskIconSizePx |
| - overviewTaskMarginGridPx; |
| overviewRowSpacing = res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing) |
| - extraTopMargin; |
| overviewGridSideMargin = res.getDimensionPixelSize(R.dimen.overview_grid_side_margin); |
| |
| // 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); |
| } |
| |
| int cellLayoutPadding = |
| isTwoPanels ? cellLayoutBorderSpacePx.x / 2 : res.getDimensionPixelSize( |
| R.dimen.cell_layout_padding); |
| cellLayoutPaddingPx = new Rect(cellLayoutPadding, cellLayoutPadding, cellLayoutPadding, |
| cellLayoutPadding); |
| 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 int calculateQsbWidth() { |
| int columns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns; |
| |
| return cellWidthPx * columns |
| + cellLayoutBorderSpacePx.x * (columns - 1) |
| - (cellWidthPx - iconSizePx) // left and right cell space |
| - iconSizePx * numShownHotseatIcons |
| - hotseatBorderSpace * numShownHotseatIcons; |
| } |
| |
| private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) { |
| if (isVerticalBarLayout()) { |
| return 0; |
| } |
| |
| return isScalableGrid |
| ? pxFromDp(idp.horizontalMargin[mTypeIndex], mMetrics) |
| : res.getDimensionPixelSize(R.dimen.dynamic_grid_left_right_margin); |
| } |
| |
| 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 Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) { |
| return getCellLayoutBorderSpace(idp, 1f); |
| |
| } |
| |
| private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale) { |
| if (!isScalableGrid) { |
| return new Point(0, 0); |
| } |
| |
| int horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics, scale); |
| int verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics, scale); |
| |
| return new Point(horizontalSpacePx, verticalSpacePx); |
| } |
| |
| 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 |
| && cellLayoutBorderSpacePx.x > widgetPadding.left |
| && cellLayoutBorderSpacePx.y > widgetPadding.top |
| && cellLayoutBorderSpacePx.x > widgetPadding.right |
| && cellLayoutBorderSpacePx.y > widgetPadding.bottom; |
| } |
| |
| public Builder toBuilder(Context context) { |
| WindowBounds bounds = new WindowBounds( |
| widthPx, heightPx, availableWidthPx, availableHeightPx, rotationHint); |
| bounds.bounds.offsetTo(windowX, windowY); |
| bounds.insets.set(mInsets); |
| return new Builder(context, inv, mInfo) |
| .setWindowBounds(bounds) |
| .setUseTwoPanels(isTwoPanels) |
| .setMultiWindowMode(isMultiWindowMode) |
| .setGestureMode(isGestureMode); |
| } |
| |
| 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); |
| |
| 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); |
| } |
| |
| private void updateAllAppsContainerWidth(Resources res) { |
| int cellLayoutHorizontalPadding = |
| (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right) / 2; |
| if (isTablet) { |
| allAppsLeftRightPadding = |
| res.getDimensionPixelSize(R.dimen.all_apps_bottom_sheet_horizontal_padding); |
| |
| int usedWidth = (allAppsCellWidthPx * numShownAllAppsColumns) |
| + (allAppsBorderSpacePx.x * (numShownAllAppsColumns - 1)) |
| + allAppsLeftRightPadding * 2; |
| allAppsLeftRightMargin = Math.max(1, (availableWidthPx - usedWidth) / 2); |
| } else { |
| allAppsLeftRightPadding = |
| desiredWorkspaceHorizontalMarginPx + cellLayoutHorizontalPadding; |
| } |
| } |
| |
| /** |
| * Returns the amount of extra (or unused) vertical space. |
| */ |
| private int updateAvailableDimensions(Resources res) { |
| updateIconSize(1f, res); |
| |
| updateWorkspacePadding(); |
| Point workspacePadding = getTotalWorkspacePadding(); |
| |
| // Check to see if the icons fit within the available height. |
| float usedHeight = getCellLayoutHeight(); |
| final int maxHeight = getWorkspaceHeight(workspacePadding); |
| 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. |
| float usedWidth = getCellLayoutWidth() + (desiredWorkspaceHorizontalMarginPx * 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) + (cellLayoutBorderSpacePx.y * (inv.numRows - 1)) |
| + cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom; |
| } |
| |
| private int getCellLayoutWidth() { |
| int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns; |
| return (cellWidthPx * numColumns) + (cellLayoutBorderSpacePx.x * (numColumns - 1)) |
| + cellLayoutPaddingPx.left + cellLayoutPaddingPx.right; |
| } |
| |
| /** |
| * 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 = inv.iconSize[mTypeIndex]; |
| float invIconTextSizeSp = inv.iconTextSize[mTypeIndex]; |
| |
| iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, iconScale)); |
| iconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * iconScale); |
| iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * iconScale); |
| |
| cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv, scale); |
| |
| if (isScalableGrid) { |
| cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale); |
| cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale); |
| int cellContentHeight = iconSizePx + iconDrawablePaddingPx |
| + Utilities.calculateTextHeight(iconTextSizePx); |
| cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2; |
| desiredWorkspaceHorizontalMarginPx = |
| (int) (desiredWorkspaceHorizontalMarginOriginalPx * 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 |
| updateAllAppsIconSize(scale, res); |
| |
| // Hotseat |
| hotseatBorderSpace = pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics, scale); |
| if (isQsbInline) { |
| qsbWidth = calculateQsbWidth(); |
| } else { |
| qsbWidth = 0; |
| } |
| updateHotseatIconSize(iconSizePx); |
| |
| // Folder icon |
| folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx); |
| folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2; |
| } |
| |
| |
| /** |
| * Updates the iconSize for allApps* variants. |
| */ |
| public void updateAllAppsIconSize(float scale, Resources res) { |
| allAppsBorderSpacePx = new Point( |
| pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics, scale), |
| pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics, scale)); |
| if (isScalableGrid) { |
| allAppsIconSizePx = |
| pxFromDp(inv.allAppsIconSize[mTypeIndex], mMetrics); |
| allAppsIconTextSizePx = |
| pxFromSp(inv.allAppsIconTextSize[mTypeIndex], mMetrics); |
| allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx; |
| // AllApps cells don't have real space between cells, |
| // so we add the border space to the cell height |
| allAppsCellHeightPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].y, mMetrics, scale) |
| + allAppsBorderSpacePx.y; |
| // but width is just the cell, |
| // the border is added in #updateAllAppsContainerWidth |
| allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics, scale); |
| } else { |
| float invIconSizeDp = inv.iconSize[mTypeIndex]; |
| float invIconTextSizeSp = inv.iconTextSize[mTypeIndex]; |
| allAppsIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale)); |
| allAppsIconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * scale); |
| allAppsIconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale); |
| allAppsCellWidthPx = allAppsIconSizePx + (2 * allAppsIconDrawablePaddingPx); |
| allAppsCellHeightPx = getCellSize().y; |
| } |
| |
| updateAllAppsContainerWidth(res); |
| if (isVerticalBarLayout()) { |
| hideWorkspaceLabelsIfNotEnoughSpace(); |
| } |
| } |
| |
| 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) * folderCellLayoutBorderSpacePx.y); |
| 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) * folderCellLayoutBorderSpacePx.x); |
| 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.iconSize[INDEX_LANDSCAPE] |
| : inv.iconSize[INDEX_DEFAULT]; |
| folderChildIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale)); |
| folderChildTextSizePx = |
| pxFromSp(inv.iconTextSize[INDEX_DEFAULT], 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 scaledSpace = (int) (folderCellLayoutBorderSpaceOriginalPx * scale); |
| folderCellLayoutBorderSpacePx = new Point(scaledSpace, scaledSpace); |
| folderContentPaddingLeftRight = scaledSpace; |
| folderContentPaddingTop = scaledSpace; |
| } 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); |
| } |
| |
| /** |
| * 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 = getWorkspaceWidth(padding); |
| result.x = calculateCellWidth(screenWidthPx, cellLayoutBorderSpacePx.x, numColumns); |
| int screenHeightPx = getWorkspaceHeight(padding); |
| result.y = calculateCellHeight(screenHeightPx, cellLayoutBorderSpacePx.y, inv.numRows); |
| return result; |
| } |
| |
| /** |
| * Gets the space in px from the bottom of last item in the vertical-bar hotseat to the |
| * bottom of the screen. |
| */ |
| public int getVerticalHotseatLastItemBottomOffset() { |
| int cellHeight = calculateCellHeight( |
| heightPx - mHotseatPadding.top - mHotseatPadding.bottom, hotseatBorderSpace, |
| numShownHotseatIcons); |
| int hotseatSize = (cellHeight * numShownHotseatIcons) |
| + (hotseatBorderSpace * (numShownHotseatIcons - 1)); |
| int extraHotseatEndSpacing = (heightPx - hotseatSize) / 2; |
| int extraIconEndSpacing = (cellHeight - iconSizePx) / 2; |
| return extraHotseatEndSpacing + extraIconEndSpacing + mHotseatPadding.bottom; |
| } |
| |
| /** |
| * Gets the scaled top of the workspace in px for the spring-loaded edit state. |
| */ |
| public float getWorkspaceSpringLoadShrunkTop() { |
| workspaceSpringLoadShrunkTop = mInsets.top + dropTargetBarTopMarginPx + dropTargetBarSizePx |
| + dropTargetBarBottomMarginPx; |
| return workspaceSpringLoadShrunkTop; |
| } |
| |
| /** |
| * Gets the scaled bottom of the workspace in px for the spring-loaded edit state. |
| */ |
| public float getWorkspaceSpringLoadShrunkBottom() { |
| int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx; |
| workspaceSpringLoadShrunkBottom = |
| heightPx - (isVerticalBarLayout() ? getVerticalHotseatLastItemBottomOffset() |
| : topOfHotseat); |
| return workspaceSpringLoadShrunkBottom; |
| } |
| |
| public int getWorkspaceWidth() { |
| return getWorkspaceWidth(getTotalWorkspacePadding()); |
| } |
| |
| public int getWorkspaceWidth(Point workspacePadding) { |
| int cellLayoutTotalPadding = |
| (isTwoPanels ? 2 : 1) * (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right); |
| return availableWidthPx - workspacePadding.x - cellLayoutTotalPadding; |
| } |
| |
| private int getWorkspaceHeight(Point workspacePadding) { |
| return availableHeightPx - workspacePadding.y - (cellLayoutPaddingPx.top |
| + cellLayoutPaddingPx.bottom); |
| } |
| |
| public Point getTotalWorkspacePadding() { |
| 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 { |
| // Pad the bottom of the workspace with search/hotseat bar sizes |
| int hotseatTop = hotseatBarSizePx; |
| int paddingBottom = hotseatTop + workspacePageIndicatorHeight |
| + workspaceBottomPadding - mWorkspacePageIndicatorOverlapWorkspace; |
| int paddingTop = workspaceTopPadding + (isScalableGrid ? 0 : edgeMarginPx); |
| int paddingSide = desiredWorkspaceHorizontalMarginPx; |
| |
| padding.set(paddingSide, paddingTop, paddingSide, paddingBottom); |
| } |
| insetPadding(workspacePadding, cellLayoutPaddingPx); |
| } |
| |
| private void insetPadding(Rect paddings, Rect insets) { |
| insets.left = Math.min(insets.left, paddings.left); |
| paddings.left -= insets.left; |
| |
| insets.top = Math.min(insets.top, paddings.top); |
| paddings.top -= insets.top; |
| |
| insets.right = Math.min(insets.right, paddings.right); |
| paddings.right -= insets.right; |
| |
| insets.bottom = Math.min(insets.bottom, paddings.bottom); |
| paddings.bottom -= insets.bottom; |
| } |
| |
| /** |
| * Returns the padding for hotseat view |
| */ |
| public Rect getHotseatLayoutPadding(Context context) { |
| if (isVerticalBarLayout()) { |
| // The hotseat icons will be placed in the middle of the hotseat cells. |
| // Changing the hotseatCellHeightPx is not affecting hotseat icon positions |
| // in vertical bar layout. |
| // Workspace icons are moved up by a small factor. The variable diffOverlapFactor |
| // is set to account for that difference. |
| float diffOverlapFactor = iconSizePx * (ICON_OVERLAP_FACTOR - 1) / 2; |
| int paddingTop = Math.max((int) (mInsets.top + cellLayoutPaddingPx.top |
| - diffOverlapFactor), 0); |
| int paddingBottom = Math.max((int) (mInsets.bottom + cellLayoutPaddingPx.bottom |
| + diffOverlapFactor), 0); |
| |
| if (isSeascape()) { |
| mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx, paddingTop, |
| hotseatBarSidePaddingEndPx, paddingBottom); |
| } else { |
| mHotseatPadding.set(hotseatBarSidePaddingEndPx, paddingTop, |
| mInsets.right + hotseatBarSidePaddingStartPx, paddingBottom); |
| } |
| } else if (isTaskbarPresent) { |
| int hotseatHeight = workspacePadding.bottom; |
| int taskbarOffset = getTaskbarOffsetY(); |
| int additionalLeftSpace = 0; |
| |
| // Center the QSB with hotseat and push icons to the right |
| if (isQsbInline) { |
| additionalLeftSpace = qsbWidth + hotseatBorderSpace; |
| } |
| |
| int hotseatTopPadding = hotseatHeight - taskbarOffset - hotseatCellHeightPx; |
| |
| int endOffset = ApiWrapper.getHotseatEndOffset(context); |
| int requiredWidth = iconSizePx * numShownHotseatIcons |
| + hotseatBorderSpace * (numShownHotseatIcons - 1) |
| + additionalLeftSpace; |
| |
| int hotseatSize = Math.min(requiredWidth, availableWidthPx - endOffset); |
| int sideSpacing = (availableWidthPx - hotseatSize) / 2; |
| mHotseatPadding.set(sideSpacing + additionalLeftSpace, hotseatTopPadding, 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 + cellLayoutPaddingPx.left |
| + mInsets.left, hotseatBarTopPaddingPx, |
| hotseatAdjustment + workspacePadding.right + cellLayoutPaddingPx.right |
| + mInsets.right, |
| hotseatBarSizePx - hotseatCellHeightPx - hotseatBarTopPaddingPx |
| + mInsets.bottom); |
| } |
| return mHotseatPadding; |
| } |
| |
| /** |
| * Returns the number of pixels the QSB is translated from the bottom of the screen. |
| */ |
| public int getQsbOffsetY() { |
| if (isQsbInline) { |
| return hotseatBarBottomPaddingPx; |
| } |
| |
| int freeSpace = isTaskbarPresent |
| ? workspacePadding.bottom |
| : hotseatBarSizePx - hotseatCellHeightPx - hotseatQsbHeight; |
| |
| if (isScalableGrid && qsbBottomMarginPx > mInsets.bottom) { |
| // Note that taskbarSize = 0 unless isTaskbarPresent. |
| return Math.min(qsbBottomMarginPx + taskbarSize, freeSpace); |
| } else { |
| return (int) (freeSpace * mQsbCenterFactor) |
| + (isTaskbarPresent ? taskbarSize : mInsets.bottom); |
| } |
| } |
| |
| /** |
| * Returns the number of pixels the taskbar is translated from the bottom of the screen. |
| */ |
| public int getTaskbarOffsetY() { |
| if (isQsbInline) { |
| return getQsbOffsetY() + (Math.abs(hotseatQsbHeight - iconSizePx) / 2); |
| } else { |
| 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; |
| // Hotseat changing sides requires updating workspace left/right paddings |
| updateWorkspacePadding(); |
| 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 + "\tisTablet:" + isTablet); |
| writer.println(prefix + "\tisPhone:" + isPhone); |
| writer.println(prefix + "\ttransposeLayoutWithOrientation:" |
| + transposeLayoutWithOrientation); |
| writer.println(prefix + "\tisGestureMode:" + isGestureMode); |
| |
| 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 + pxToDpStr("mInsets.left", mInsets.left)); |
| writer.println(prefix + pxToDpStr("mInsets.top", mInsets.top)); |
| writer.println(prefix + pxToDpStr("mInsets.right", mInsets.right)); |
| writer.println(prefix + pxToDpStr("mInsets.bottom", mInsets.bottom)); |
| |
| writer.println(prefix + "\taspectRatio:" + aspectRatio); |
| |
| writer.println(prefix + "\tisScalableGrid:" + isScalableGrid); |
| |
| writer.println(prefix + "\tinv.numRows: " + inv.numRows); |
| writer.println(prefix + "\tinv.numColumns: " + inv.numColumns); |
| writer.println(prefix + "\tinv.numSearchContainerColumns: " |
| + inv.numSearchContainerColumns); |
| |
| writer.println(prefix + "\tminCellSize: " + inv.minCellSize[mTypeIndex] + "dp"); |
| |
| 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 + pxToDpStr("cellLayoutBorderSpacePx Horizontal", |
| cellLayoutBorderSpacePx.x)); |
| writer.println(prefix + pxToDpStr("cellLayoutBorderSpacePx Vertical", |
| cellLayoutBorderSpacePx.y)); |
| writer.println(prefix + pxToDpStr("cellLayoutPaddingPx.left", cellLayoutPaddingPx.left)); |
| writer.println(prefix + pxToDpStr("cellLayoutPaddingPx.top", cellLayoutPaddingPx.top)); |
| writer.println(prefix + pxToDpStr("cellLayoutPaddingPx.right", cellLayoutPaddingPx.right)); |
| writer.println( |
| prefix + pxToDpStr("cellLayoutPaddingPx.bottom", cellLayoutPaddingPx.bottom)); |
| |
| 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("folderCellLayoutBorderSpaceOriginalPx", |
| folderCellLayoutBorderSpaceOriginalPx)); |
| writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx Horizontal", |
| folderCellLayoutBorderSpacePx.x)); |
| writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx Vertical", |
| folderCellLayoutBorderSpacePx.y)); |
| |
| writer.println(prefix + pxToDpStr("bottomSheetTopPadding", bottomSheetTopPadding)); |
| |
| writer.println(prefix + pxToDpStr("allAppsShiftRange", allAppsShiftRange)); |
| writer.println(prefix + pxToDpStr("allAppsTopPadding", allAppsTopPadding)); |
| 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 + pxToDpStr("allAppsCellWidthPx", allAppsCellWidthPx)); |
| writer.println(prefix + pxToDpStr("allAppsBorderSpacePx", allAppsBorderSpacePx.x)); |
| writer.println(prefix + "\tnumShownAllAppsColumns: " + numShownAllAppsColumns); |
| writer.println(prefix + pxToDpStr("allAppsLeftRightPadding", allAppsLeftRightPadding)); |
| writer.println(prefix + pxToDpStr("allAppsLeftRightMargin", allAppsLeftRightMargin)); |
| |
| 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 + pxToDpStr("springLoadedHotseatBarTopMarginPx", |
| springLoadedHotseatBarTopMarginPx)); |
| writer.println(prefix + pxToDpStr("mHotseatPadding.top", mHotseatPadding.top)); |
| writer.println(prefix + pxToDpStr("mHotseatPadding.bottom", mHotseatPadding.bottom)); |
| writer.println(prefix + pxToDpStr("mHotseatPadding.left", mHotseatPadding.left)); |
| writer.println(prefix + pxToDpStr("mHotseatPadding.right", mHotseatPadding.right)); |
| writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons); |
| writer.println(prefix + pxToDpStr("hotseatBorderSpace", hotseatBorderSpace)); |
| writer.println(prefix + "\tisQsbInline: " + isQsbInline); |
| writer.println(prefix + pxToDpStr("qsbWidth", qsbWidth)); |
| |
| writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent); |
| writer.println(prefix + "\tisTaskbarPresentInApps:" + isTaskbarPresentInApps); |
| writer.println(prefix + pxToDpStr("taskbarSize", taskbarSize)); |
| |
| writer.println(prefix + pxToDpStr("desiredWorkspaceHorizontalMarginPx", |
| desiredWorkspaceHorizontalMarginPx)); |
| 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)); |
| writer.println(prefix + pxToDpStr("unscaled extraSpace", extraSpace / iconScale)); |
| |
| 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)); |
| |
| writer.println(prefix + pxToDpStr("overviewTaskMarginPx", overviewTaskMarginPx)); |
| writer.println(prefix + pxToDpStr("overviewTaskMarginGridPx", overviewTaskMarginGridPx)); |
| writer.println(prefix + pxToDpStr("overviewTaskIconSizePx", overviewTaskIconSizePx)); |
| writer.println(prefix + pxToDpStr("overviewTaskIconDrawableSizePx", |
| overviewTaskIconDrawableSizePx)); |
| writer.println(prefix + pxToDpStr("overviewTaskIconDrawableSizeGridPx", |
| overviewTaskIconDrawableSizeGridPx)); |
| writer.println(prefix + pxToDpStr("overviewTaskThumbnailTopMarginPx", |
| overviewTaskThumbnailTopMarginPx)); |
| writer.println(prefix + pxToDpStr("overviewActionsMarginThreeButtonPx", |
| overviewActionsMarginThreeButtonPx)); |
| writer.println(prefix + pxToDpStr("overviewActionsTopMarginGesturePx", |
| overviewActionsTopMarginGesturePx)); |
| writer.println(prefix + pxToDpStr("overviewActionsBottomMarginGesturePx", |
| overviewActionsBottomMarginGesturePx)); |
| writer.println(prefix + pxToDpStr("overviewActionsButtonSpacing", |
| overviewActionsButtonSpacing)); |
| writer.println(prefix + pxToDpStr("overviewPageSpacing", overviewPageSpacing)); |
| writer.println(prefix + pxToDpStr("overviewRowSpacing", overviewRowSpacing)); |
| writer.println(prefix + pxToDpStr("overviewGridSideMargin", overviewGridSideMargin)); |
| |
| writer.println(prefix + pxToDpStr("dropTargetBarTopMarginPx", dropTargetBarTopMarginPx)); |
| writer.println(prefix + pxToDpStr("dropTargetBarSizePx", dropTargetBarSizePx)); |
| writer.println( |
| prefix + pxToDpStr("dropTargetBarBottomMarginPx", dropTargetBarBottomMarginPx)); |
| |
| writer.println( |
| prefix + pxToDpStr("workspaceSpringLoadShrunkTop", workspaceSpringLoadShrunkTop)); |
| writer.println(prefix + pxToDpStr("workspaceSpringLoadShrunkBottom", |
| workspaceSpringLoadShrunkBottom)); |
| } |
| |
| private static Context getContext(Context c, Info info, int orientation, WindowBounds bounds) { |
| Configuration config = new Configuration(c.getResources().getConfiguration()); |
| config.orientation = orientation; |
| config.densityDpi = info.densityDpi; |
| config.smallestScreenWidthDp = (int) info.smallestSizeDp(bounds); |
| 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); |
| } |
| |
| /** Allows registering listeners for {@link DeviceProfile} changes. */ |
| public interface DeviceProfileListenable { |
| |
| /** The current device profile. */ |
| DeviceProfile getDeviceProfile(); |
| |
| /** Registered {@link OnDeviceProfileChangeListener} instances. */ |
| List<OnDeviceProfileChangeListener> getOnDeviceProfileChangeListeners(); |
| |
| /** Notifies listeners of a {@link DeviceProfile} change. */ |
| default void dispatchDeviceProfileChanged() { |
| DeviceProfile deviceProfile = getDeviceProfile(); |
| List<OnDeviceProfileChangeListener> listeners = getOnDeviceProfileChangeListeners(); |
| for (int i = listeners.size() - 1; i >= 0; i--) { |
| listeners.get(i).onDeviceProfileChanged(deviceProfile); |
| } |
| } |
| |
| /** Register listener for {@link DeviceProfile} changes. */ |
| default void addOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) { |
| getOnDeviceProfileChangeListeners().add(listener); |
| } |
| |
| /** Unregister listener for {@link DeviceProfile} changes. */ |
| default void removeOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) { |
| getOnDeviceProfileChangeListeners().remove(listener); |
| } |
| } |
| |
| 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; |
| private Boolean mIsGestureMode; |
| |
| 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 Builder setGestureMode(boolean isGestureMode) { |
| mIsGestureMode = isGestureMode; |
| return this; |
| } |
| |
| public DeviceProfile build() { |
| if (mWindowBounds == null) { |
| throw new IllegalArgumentException("Window bounds not set"); |
| } |
| if (mTransposeLayoutWithOrientation == null) { |
| mTransposeLayoutWithOrientation = !mInfo.isTablet(mWindowBounds); |
| } |
| if (mIsGestureMode == null) { |
| mIsGestureMode = DisplayController.getNavigationMode(mContext).hasGestures; |
| } |
| return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, mIsMultiWindowMode, |
| mTransposeLayoutWithOrientation, mUseTwoPanels, mIsGestureMode); |
| } |
| } |
| |
| } |