1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3; 18 19 import static com.android.app.animation.Interpolators.LINEAR; 20 import static com.android.launcher3.Flags.enableOverviewIconMenu; 21 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; 22 import static com.android.launcher3.InvariantDeviceProfile.INDEX_DEFAULT; 23 import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE; 24 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE; 25 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT; 26 import static com.android.launcher3.Utilities.dpiFromPx; 27 import static com.android.launcher3.Utilities.pxFromSp; 28 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR; 29 import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE; 30 import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp; 31 import static com.android.launcher3.testing.shared.ResourceUtils.roundPxValueFromFloat; 32 import static com.android.wm.shell.Flags.enableBubbleBar; 33 import static com.android.wm.shell.Flags.enableBubbleBarOnPhones; 34 import static com.android.wm.shell.Flags.enableTinyTaskbar; 35 36 import android.annotation.SuppressLint; 37 import android.content.Context; 38 import android.content.res.Configuration; 39 import android.content.res.Resources; 40 import android.content.res.TypedArray; 41 import android.graphics.Point; 42 import android.graphics.PointF; 43 import android.graphics.Rect; 44 import android.util.DisplayMetrics; 45 import android.util.SparseArray; 46 import android.view.Surface; 47 48 import androidx.annotation.NonNull; 49 import androidx.annotation.Nullable; 50 import androidx.annotation.VisibleForTesting; 51 import androidx.core.content.res.ResourcesCompat; 52 53 import com.android.launcher3.CellLayout.ContainerType; 54 import com.android.launcher3.DevicePaddings.DevicePadding; 55 import com.android.launcher3.folder.ClippedFolderIconLayoutRule; 56 import com.android.launcher3.graphics.ThemeManager; 57 import com.android.launcher3.icons.DotRenderer; 58 import com.android.launcher3.model.data.ItemInfo; 59 import com.android.launcher3.responsive.CalculatedCellSpec; 60 import com.android.launcher3.responsive.CalculatedHotseatSpec; 61 import com.android.launcher3.responsive.CalculatedResponsiveSpec; 62 import com.android.launcher3.responsive.HotseatSpecsProvider; 63 import com.android.launcher3.responsive.ResponsiveCellSpecsProvider; 64 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType; 65 import com.android.launcher3.responsive.ResponsiveSpec.DimensionType; 66 import com.android.launcher3.responsive.ResponsiveSpecsProvider; 67 import com.android.launcher3.util.CellContentDimensions; 68 import com.android.launcher3.util.DisplayController; 69 import com.android.launcher3.util.DisplayController.Info; 70 import com.android.launcher3.util.IconSizeSteps; 71 import com.android.launcher3.util.ResourceHelper; 72 import com.android.launcher3.util.WindowBounds; 73 import com.android.launcher3.util.window.WindowManagerProxy; 74 75 import java.io.PrintWriter; 76 import java.util.Locale; 77 import java.util.function.Consumer; 78 79 @SuppressLint("NewApi") 80 public class DeviceProfile { 81 82 private static final int DEFAULT_DOT_SIZE = 100; 83 private static final float MIN_FOLDER_TEXT_SIZE_SP = 16f; 84 private static final float MIN_WIDGET_PADDING_DP = 6f; 85 86 // Minimum aspect ratio beyond which an extra top padding may be applied to a bottom sheet. 87 private static final float MIN_ASPECT_RATIO_FOR_EXTRA_TOP_PADDING = 1.5f; 88 private static final float MAX_ASPECT_RATIO_FOR_ALTERNATE_EDIT_STATE = 1.5f; 89 90 public static final PointF DEFAULT_SCALE = new PointF(1.0f, 1.0f); 91 public static final ViewScaleProvider DEFAULT_PROVIDER = itemInfo -> DEFAULT_SCALE; 92 public static final Consumer<DeviceProfile> DEFAULT_DIMENSION_PROVIDER = dp -> { 93 }; 94 95 public final InvariantDeviceProfile inv; 96 private final Info mInfo; 97 private final DisplayMetrics mMetrics; 98 private final IconSizeSteps mIconSizeSteps; 99 100 // Device properties 101 public final boolean isTablet; 102 public final boolean isPhone; 103 public final boolean transposeLayoutWithOrientation; 104 public final boolean isMultiDisplay; 105 public final boolean isTwoPanels; 106 public boolean isPredictiveBackSwipe; 107 public final boolean isQsbInline; 108 109 // Device properties in current orientation 110 public final boolean isLandscape; 111 public final boolean isMultiWindowMode; 112 public final boolean isGestureMode; 113 114 public final boolean isLeftRightSplit; 115 116 public final int windowX; 117 public final int windowY; 118 public final int widthPx; 119 public final int heightPx; 120 public final int availableWidthPx; 121 public final int availableHeightPx; 122 public final int rotationHint; 123 124 public final float aspectRatio; 125 126 private final boolean mIsScalableGrid; 127 private final int mTypeIndex; 128 129 // Responsive grid 130 private final boolean mIsResponsiveGrid; 131 private CalculatedResponsiveSpec mResponsiveWorkspaceWidthSpec; 132 private CalculatedResponsiveSpec mResponsiveWorkspaceHeightSpec; 133 private CalculatedResponsiveSpec mResponsiveAllAppsWidthSpec; 134 private CalculatedResponsiveSpec mResponsiveAllAppsHeightSpec; 135 private CalculatedResponsiveSpec mResponsiveFolderWidthSpec; 136 private CalculatedResponsiveSpec mResponsiveFolderHeightSpec; 137 private CalculatedHotseatSpec mResponsiveHotseatSpec; 138 private CalculatedCellSpec mResponsiveWorkspaceCellSpec; 139 private CalculatedCellSpec mResponsiveAllAppsCellSpec; 140 141 /** 142 * The maximum amount of left/right workspace padding as a percentage of the screen width. 143 * To be clear, this means that up to 7% of the screen width can be used as left padding, and 144 * 7% of the screen width can be used as right padding. 145 */ 146 private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f; 147 148 private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f; 149 private static final float TALLER_DEVICE_ASPECT_RATIO_THRESHOLD = 2.15f; 150 private static final float TALL_DEVICE_EXTRA_SPACE_THRESHOLD_DP = 252; 151 private static final float TALL_DEVICE_MORE_EXTRA_SPACE_THRESHOLD_DP = 268; 152 153 // Workspace 154 public final int desiredWorkspaceHorizontalMarginOriginalPx; 155 public int desiredWorkspaceHorizontalMarginPx; 156 public int gridVisualizationPaddingX; 157 public int gridVisualizationPaddingY; 158 public Point cellLayoutBorderSpaceOriginalPx; 159 public Point cellLayoutBorderSpacePx; 160 public Rect cellLayoutPaddingPx = new Rect(); 161 162 public final int edgeMarginPx; 163 public final float workspaceContentScale; 164 public final int workspaceSpringLoadedMinNextPageVisiblePx; 165 166 private final int extraSpace; 167 private int maxEmptySpace; 168 public int workspaceTopPadding; 169 public int workspaceBottomPadding; 170 171 // Workspace page indicator 172 public final int workspacePageIndicatorHeight; 173 private final int mWorkspacePageIndicatorOverlapWorkspace; 174 175 // Workspace icons 176 public float iconScale; 177 public int iconSizePx; 178 public int iconTextSizePx; 179 public int iconDrawablePaddingPx; 180 private int mIconDrawablePaddingOriginalPx; 181 public boolean iconCenterVertically; 182 183 public float cellScaleToFit; 184 public int cellWidthPx; 185 public int cellHeightPx; 186 public int workspaceCellPaddingXPx; 187 188 public int cellYPaddingPx = -1; 189 190 // Folder 191 public final int numFolderRows; 192 public final int numFolderColumns; 193 public final float folderLabelTextScale; 194 public int folderLabelTextSizePx; 195 public int folderFooterHeightPx; 196 public int folderIconSizePx; 197 public int folderIconOffsetYPx; 198 199 // Folder content 200 public Point folderCellLayoutBorderSpacePx; 201 public int folderContentPaddingLeftRight; 202 public int folderContentPaddingTop; 203 204 // Folder cell 205 public int folderCellWidthPx; 206 public int folderCellHeightPx; 207 208 // Folder child 209 public int folderChildIconSizePx; 210 public int folderChildTextSizePx; 211 public int folderChildDrawablePaddingPx; 212 213 // Hotseat 214 public int numShownHotseatIcons; 215 public int hotseatCellHeightPx; 216 private int mHotseatColumnSpan; 217 private int mHotseatWidthPx; // not used in vertical bar layout 218 public final boolean areNavButtonsInline; 219 // In portrait: size = height, in landscape: size = width 220 public int hotseatBarSizePx; 221 public int hotseatBarBottomSpacePx; 222 public int hotseatBarEndOffset; 223 public int hotseatQsbSpace; 224 public int inlineNavButtonsEndSpacingPx; 225 public int navButtonsLayoutWidthPx; 226 public int springLoadedHotseatBarTopMarginPx; 227 // These 2 values are only used for isVerticalBar 228 // Padding between edge of screen and hotseat 229 public final int mHotseatBarEdgePaddingPx; 230 // Space between hotseat and workspace (not used in responsive) 231 public final int mHotseatBarWorkspaceSpacePx; 232 public int hotseatQsbWidth; // only used when isQsbInline 233 public final int hotseatQsbHeight; 234 public final int hotseatQsbVisualHeight; 235 private final int hotseatQsbShadowHeight; 236 public int hotseatBorderSpace; 237 private final int mMinHotseatIconSpacePx; 238 private final int mMinHotseatQsbWidthPx; 239 private final int mMaxHotseatIconSpacePx; 240 // Space required for the bubble bar between the hotseat and the edge of the screen. If there's 241 // not enough space, the hotseat will adjust itself for the bubble bar. 242 private final int mBubbleBarSpaceThresholdPx; 243 244 // Bottom sheets 245 public int bottomSheetTopPadding; 246 public int bottomSheetOpenDuration; 247 public int bottomSheetCloseDuration; 248 public float bottomSheetWorkspaceScale; 249 public float bottomSheetDepth; 250 251 // All apps 252 public Point allAppsBorderSpacePx; 253 public int allAppsShiftRange; 254 public Rect allAppsPadding = new Rect(); 255 public int allAppsOpenDuration; 256 public int allAppsCloseDuration; 257 public int allAppsCellHeightPx; 258 public int allAppsCellWidthPx; 259 public int allAppsIconSizePx; 260 public int allAppsIconDrawablePaddingPx; 261 public int allAppsLeftRightMargin; 262 public final int numShownAllAppsColumns; 263 public float allAppsIconTextSizePx; 264 265 // Overview 266 public int overviewTaskMarginPx; 267 public int overviewTaskIconSizePx; 268 public int overviewTaskIconDrawableSizePx; 269 public int overviewTaskIconDrawableSizeGridPx; 270 public int overviewTaskThumbnailTopMarginPx; 271 public final int overviewActionsHeight; 272 public final int overviewActionsTopMarginPx; 273 public final int overviewActionsButtonSpacing; 274 public int overviewPageSpacing; 275 public int overviewRowSpacing; 276 public int overviewGridSideMargin; 277 278 // Split staging 279 public int splitPlaceholderInset; 280 281 // Widgets 282 private final ViewScaleProvider mViewScaleProvider; 283 284 // Drop Target 285 public int dropTargetBarSizePx; 286 public int dropTargetBarTopMarginPx; 287 public int dropTargetBarBottomMarginPx; 288 public int dropTargetDragPaddingPx; 289 public int dropTargetTextSizePx; 290 public int dropTargetHorizontalPaddingPx; 291 public int dropTargetVerticalPaddingPx; 292 public int dropTargetGapPx; 293 public int dropTargetButtonWorkspaceEdgeGapPx; 294 295 // Insets 296 private final Rect mInsets = new Rect(); 297 public final Rect workspacePadding = new Rect(); 298 // Additional padding added to the widget inside its cellSpace. It is applied outside 299 // the widgetView, such that the actual view size is same as the widget size. 300 public final Rect widgetPadding = new Rect(); 301 302 // Notification dots 303 public final DotRenderer mDotRendererWorkSpace; 304 public final DotRenderer mDotRendererAllApps; 305 306 // Taskbar 307 public boolean isTaskbarPresent; 308 // Whether Taskbar will inset the bottom of apps by taskbarSize. 309 public boolean isTaskbarPresentInApps; 310 public final int taskbarHeight; 311 public final int stashedTaskbarHeight; 312 public final int taskbarBottomMargin; 313 public final int taskbarIconSize; 314 private final int mTransientTaskbarClaimedSpace; 315 // If true, used to layout taskbar in 3 button navigation mode. 316 public final boolean startAlignTaskbar; 317 public final boolean isTransientTaskbar; 318 // DragController 319 public int flingToDeleteThresholdVelocity; 320 321 /** Used only as an alternative to mocking when null values cannot be used. */ 322 @VisibleForTesting DeviceProfile()323 public DeviceProfile() { 324 inv = null; 325 mInfo = null; 326 mMetrics = null; 327 mIconSizeSteps = null; 328 isTablet = false; 329 isPhone = false; 330 transposeLayoutWithOrientation = false; 331 isMultiDisplay = false; 332 isTwoPanels = false; 333 isPredictiveBackSwipe = false; 334 isQsbInline = false; 335 isLandscape = false; 336 isMultiWindowMode = false; 337 isGestureMode = false; 338 isLeftRightSplit = false; 339 windowX = 0; 340 windowY = 0; 341 widthPx = 0; 342 heightPx = 0; 343 availableWidthPx = 0; 344 availableHeightPx = 0; 345 rotationHint = 0; 346 aspectRatio = 1; 347 mIsScalableGrid = false; 348 mTypeIndex = 0; 349 mIsResponsiveGrid = false; 350 desiredWorkspaceHorizontalMarginOriginalPx = 0; 351 edgeMarginPx = 0; 352 workspaceContentScale = 0; 353 workspaceSpringLoadedMinNextPageVisiblePx = 0; 354 extraSpace = 0; 355 workspacePageIndicatorHeight = 0; 356 mWorkspacePageIndicatorOverlapWorkspace = 0; 357 numFolderRows = 0; 358 numFolderColumns = 0; 359 folderLabelTextScale = 0; 360 areNavButtonsInline = false; 361 mHotseatBarEdgePaddingPx = 0; 362 mHotseatBarWorkspaceSpacePx = 0; 363 hotseatQsbWidth = 0; 364 hotseatQsbHeight = 0; 365 hotseatQsbVisualHeight = 0; 366 hotseatQsbShadowHeight = 0; 367 hotseatBorderSpace = 0; 368 mMinHotseatIconSpacePx = 0; 369 mMinHotseatQsbWidthPx = 0; 370 mMaxHotseatIconSpacePx = 0; 371 inlineNavButtonsEndSpacingPx = 0; 372 mBubbleBarSpaceThresholdPx = 0; 373 numShownAllAppsColumns = 0; 374 overviewActionsHeight = 0; 375 overviewActionsTopMarginPx = 0; 376 overviewActionsButtonSpacing = 0; 377 mViewScaleProvider = null; 378 mDotRendererWorkSpace = null; 379 mDotRendererAllApps = null; 380 taskbarHeight = 0; 381 stashedTaskbarHeight = 0; 382 taskbarBottomMargin = 0; 383 taskbarIconSize = 0; 384 mTransientTaskbarClaimedSpace = 0; 385 startAlignTaskbar = false; 386 isTransientTaskbar = false; 387 } 388 389 /** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */ DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowManagerProxy wmProxy, ThemeManager themeManager, WindowBounds windowBounds, SparseArray<DotRenderer> dotRendererCache, boolean isMultiWindowMode, boolean transposeLayoutWithOrientation, boolean isMultiDisplay, boolean isGestureMode, @NonNull final ViewScaleProvider viewScaleProvider, @NonNull final Consumer<DeviceProfile> dimensionOverrideProvider, boolean isTransientTaskbar)390 DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, 391 WindowManagerProxy wmProxy, ThemeManager themeManager, WindowBounds windowBounds, 392 SparseArray<DotRenderer> dotRendererCache, boolean isMultiWindowMode, 393 boolean transposeLayoutWithOrientation, boolean isMultiDisplay, boolean isGestureMode, 394 @NonNull final ViewScaleProvider viewScaleProvider, 395 @NonNull final Consumer<DeviceProfile> dimensionOverrideProvider, 396 boolean isTransientTaskbar) { 397 398 this.inv = inv; 399 this.isLandscape = windowBounds.isLandscape(); 400 this.isMultiWindowMode = isMultiWindowMode; 401 this.transposeLayoutWithOrientation = transposeLayoutWithOrientation; 402 this.isMultiDisplay = isMultiDisplay; 403 this.isGestureMode = isGestureMode; 404 windowX = windowBounds.bounds.left; 405 windowY = windowBounds.bounds.top; 406 this.rotationHint = windowBounds.rotationHint; 407 mInsets.set(windowBounds.insets); 408 409 // TODO(b/241386436): shouldn't change any launcher behaviour 410 mIsResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE 411 && inv.allAppsSpecsId != INVALID_RESOURCE_HANDLE 412 && inv.folderSpecsId != INVALID_RESOURCE_HANDLE 413 && inv.hotseatSpecsId != INVALID_RESOURCE_HANDLE 414 && inv.workspaceCellSpecsId != INVALID_RESOURCE_HANDLE 415 && inv.allAppsCellSpecsId != INVALID_RESOURCE_HANDLE; 416 417 mIsScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode; 418 // Determine device posture. 419 mInfo = info; 420 isTablet = info.isTablet(windowBounds); 421 isPhone = !isTablet; 422 isTwoPanels = isTablet && isMultiDisplay; 423 boolean taskbarOrBubbleBarOnPhones = enableTinyTaskbar() 424 || (enableBubbleBar() && enableBubbleBarOnPhones()); 425 isTaskbarPresent = (isTablet || (taskbarOrBubbleBarOnPhones && isGestureMode)) 426 && wmProxy.isTaskbarDrawnInProcess(); 427 428 // Some more constants. 429 context = getContext(context, info, inv.isFixedLandscape 430 || isVerticalBarLayout() 431 || (isTablet && isLandscape) 432 ? Configuration.ORIENTATION_LANDSCAPE 433 : Configuration.ORIENTATION_PORTRAIT, 434 windowBounds); 435 final Resources res = context.getResources(); 436 mMetrics = res.getDisplayMetrics(); 437 438 mIconSizeSteps = new IconSizeSteps(res); 439 440 // Determine sizes. 441 widthPx = windowBounds.bounds.width(); 442 heightPx = windowBounds.bounds.height(); 443 availableWidthPx = windowBounds.availableSize.x; 444 availableHeightPx = windowBounds.availableSize.y; 445 446 aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx); 447 if (isTwoPanels) { 448 if (isLandscape) { 449 mTypeIndex = INDEX_TWO_PANEL_LANDSCAPE; 450 } else { 451 mTypeIndex = INDEX_TWO_PANEL_PORTRAIT; 452 } 453 } else { 454 if (isLandscape) { 455 mTypeIndex = INDEX_LANDSCAPE; 456 } else { 457 mTypeIndex = INDEX_DEFAULT; 458 } 459 } 460 461 this.isTransientTaskbar = isTransientTaskbar; 462 int transientTaskbarIconSize = pxFromDp(inv.transientTaskbarIconSize[mTypeIndex], mMetrics); 463 int transientTaskbarBottomMargin = 464 res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin); 465 int transientTaskbarHeight = 466 Math.round((transientTaskbarIconSize * ICON_VISIBLE_AREA_FACTOR) 467 + (2 * res.getDimensionPixelSize(R.dimen.transient_taskbar_padding))); 468 mTransientTaskbarClaimedSpace = transientTaskbarHeight + 2 * transientTaskbarBottomMargin; 469 470 if (!isTaskbarPresent) { 471 taskbarIconSize = taskbarHeight = stashedTaskbarHeight = taskbarBottomMargin = 0; 472 startAlignTaskbar = false; 473 } else if (isTransientTaskbar) { 474 taskbarIconSize = transientTaskbarIconSize; 475 taskbarHeight = transientTaskbarHeight; 476 stashedTaskbarHeight = 477 res.getDimensionPixelSize(R.dimen.transient_taskbar_stashed_height); 478 taskbarBottomMargin = transientTaskbarBottomMargin; 479 startAlignTaskbar = false; 480 } else { 481 taskbarIconSize = pxFromDp(ResourcesCompat.getFloat(res, R.dimen.taskbar_icon_size), 482 mMetrics); 483 taskbarHeight = res.getDimensionPixelSize(R.dimen.taskbar_size); 484 stashedTaskbarHeight = res.getDimensionPixelSize(R.dimen.taskbar_stashed_size); 485 taskbarBottomMargin = 0; 486 startAlignTaskbar = inv.startAlignTaskbar[mTypeIndex]; 487 } 488 489 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin); 490 workspaceContentScale = res.getFloat(R.dimen.workspace_content_scale); 491 492 gridVisualizationPaddingX = res.getDimensionPixelSize( 493 R.dimen.grid_visualization_horizontal_cell_spacing); 494 gridVisualizationPaddingY = res.getDimensionPixelSize( 495 R.dimen.grid_visualization_vertical_cell_spacing); 496 497 { 498 // In large screens, in portrait mode, a bottom sheet can appear too elongated, so, we 499 // apply additional padding. 500 final boolean applyExtraTopPadding = isTablet 501 && !isLandscape 502 && (aspectRatio > MIN_ASPECT_RATIO_FOR_EXTRA_TOP_PADDING); 503 final int derivedTopPadding = heightPx / 6; 504 bottomSheetTopPadding = mInsets.top // statusbar height 505 + (applyExtraTopPadding ? derivedTopPadding : 0) 506 + (isTablet ? 0 : edgeMarginPx); // phones need edgeMarginPx additional padding 507 } 508 509 bottomSheetOpenDuration = res.getInteger(R.integer.config_bottomSheetOpenDuration); 510 bottomSheetCloseDuration = res.getInteger(R.integer.config_bottomSheetCloseDuration); 511 if (shouldShowAllAppsOnSheet()) { 512 bottomSheetWorkspaceScale = workspaceContentScale; 513 if (Flags.allAppsBlur()) { 514 bottomSheetDepth = 2f; 515 } else if (isMultiDisplay) { 516 // TODO(b/259893832): Revert to use maxWallpaperScale to calculate bottomSheetDepth 517 // when screen recorder bug is fixed. 518 if (enableScalingRevealHomeAnimation()) { 519 bottomSheetDepth = 0.3f; 520 } else { 521 bottomSheetDepth = 1f; 522 } 523 } else { 524 // The goal is to set wallpaper to zoom at workspaceContentScale when in AllApps. 525 // When depth is 0, wallpaper zoom is set to maxWallpaperScale. 526 // When depth is 1, wallpaper zoom is set to 1. 527 // For depth to achieve zoom set to maxWallpaperScale * workspaceContentScale: 528 float maxWallpaperScale = res.getFloat(R.dimen.config_wallpaperMaxScale); 529 bottomSheetDepth = Utilities.mapToRange(maxWallpaperScale * workspaceContentScale, 530 maxWallpaperScale, 1f, 0f, 1f, LINEAR); 531 } 532 } else { 533 bottomSheetWorkspaceScale = 1f; 534 bottomSheetDepth = 0f; 535 } 536 537 folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale); 538 numFolderRows = inv.numFolderRows[mTypeIndex]; 539 numFolderColumns = inv.numFolderColumns[mTypeIndex]; 540 541 if (mIsScalableGrid && inv.folderStyle != INVALID_RESOURCE_HANDLE) { 542 TypedArray folderStyle = context.obtainStyledAttributes(inv.folderStyle, 543 R.styleable.FolderStyle); 544 // These are re-set in #updateFolderCellSize if the grid is not scalable 545 folderCellHeightPx = folderStyle.getDimensionPixelSize( 546 R.styleable.FolderStyle_folderCellHeight, 0); 547 folderCellWidthPx = folderStyle.getDimensionPixelSize( 548 R.styleable.FolderStyle_folderCellWidth, 0); 549 550 folderContentPaddingTop = folderStyle.getDimensionPixelSize( 551 R.styleable.FolderStyle_folderTopPadding, 0); 552 553 int gutter = folderStyle.getDimensionPixelSize( 554 R.styleable.FolderStyle_folderBorderSpace, 0); 555 folderCellLayoutBorderSpacePx = new Point(gutter, gutter); 556 folderFooterHeightPx = folderStyle.getDimensionPixelSize( 557 R.styleable.FolderStyle_folderFooterHeight, 0); 558 folderStyle.recycle(); 559 } else if (!mIsResponsiveGrid) { 560 folderCellLayoutBorderSpacePx = new Point(0, 0); 561 folderFooterHeightPx = res.getDimensionPixelSize(R.dimen.folder_footer_height_default); 562 folderContentPaddingTop = res.getDimensionPixelSize(R.dimen.folder_top_padding_default); 563 } 564 565 allAppsBorderSpacePx = new Point( 566 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics), 567 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics)); 568 setupAllAppsStyle(context); 569 570 workspacePageIndicatorHeight = res.getDimensionPixelSize( 571 R.dimen.workspace_page_indicator_height); 572 mWorkspacePageIndicatorOverlapWorkspace = 573 res.getDimensionPixelSize(R.dimen.workspace_page_indicator_overlap_workspace); 574 575 if (!mIsResponsiveGrid) { 576 TypedArray cellStyle; 577 if (inv.cellStyle != INVALID_RESOURCE_HANDLE) { 578 cellStyle = context.obtainStyledAttributes(inv.cellStyle, 579 R.styleable.CellStyle); 580 } else { 581 cellStyle = context.obtainStyledAttributes(R.style.CellStyleDefault, 582 R.styleable.CellStyle); 583 } 584 mIconDrawablePaddingOriginalPx = cellStyle.getDimensionPixelSize( 585 R.styleable.CellStyle_iconDrawablePadding, 0); 586 cellStyle.recycle(); 587 } 588 589 dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size); 590 // Some foldable portrait modes are too wide in terms of aspect ratio so we need to tweak 591 // the dimensions for edit state. 592 final boolean shouldApplyWidePortraitDimens = isTablet 593 && !isLandscape 594 && aspectRatio < MAX_ASPECT_RATIO_FOR_ALTERNATE_EDIT_STATE; 595 dropTargetBarTopMarginPx = shouldApplyWidePortraitDimens 596 ? 0 597 : res.getDimensionPixelSize(R.dimen.drop_target_top_margin); 598 dropTargetBarBottomMarginPx = shouldApplyWidePortraitDimens 599 ? res.getDimensionPixelSize(R.dimen.drop_target_bottom_margin_wide_portrait) 600 : res.getDimensionPixelSize(R.dimen.drop_target_bottom_margin); 601 dropTargetDragPaddingPx = res.getDimensionPixelSize(R.dimen.drop_target_drag_padding); 602 dropTargetTextSizePx = res.getDimensionPixelSize(R.dimen.drop_target_text_size); 603 dropTargetHorizontalPaddingPx = res.getDimensionPixelSize( 604 R.dimen.drop_target_button_drawable_horizontal_padding); 605 dropTargetVerticalPaddingPx = res.getDimensionPixelSize( 606 R.dimen.drop_target_button_drawable_vertical_padding); 607 dropTargetGapPx = res.getDimensionPixelSize(R.dimen.drop_target_button_gap); 608 dropTargetButtonWorkspaceEdgeGapPx = res.getDimensionPixelSize( 609 R.dimen.drop_target_button_workspace_edge_gap); 610 611 workspaceSpringLoadedMinNextPageVisiblePx = res.getDimensionPixelSize( 612 R.dimen.dynamic_grid_spring_loaded_min_next_space_visible); 613 614 workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x); 615 616 hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height); 617 hotseatQsbShadowHeight = res.getDimensionPixelSize(R.dimen.qsb_shadow_height); 618 hotseatQsbVisualHeight = hotseatQsbHeight - 2 * hotseatQsbShadowHeight; 619 620 // Whether QSB might be inline in appropriate orientation (e.g. landscape). 621 boolean canQsbInline = (isTwoPanels ? inv.inlineQsb[INDEX_TWO_PANEL_PORTRAIT] 622 || inv.inlineQsb[INDEX_TWO_PANEL_LANDSCAPE] 623 : inv.inlineQsb[INDEX_DEFAULT] || inv.inlineQsb[INDEX_LANDSCAPE]) 624 && hotseatQsbHeight > 0; 625 isQsbInline = isQsbInline(inv); 626 627 areNavButtonsInline = isTaskbarPresent && !isGestureMode; 628 numShownHotseatIcons = 629 isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons; 630 mHotseatColumnSpan = inv.numColumns; 631 632 numShownAllAppsColumns = 633 isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns; 634 635 int hotseatBarBottomSpace; 636 int minQsbMargin = res.getDimensionPixelSize(R.dimen.min_qsb_margin); 637 638 if (mIsResponsiveGrid) { 639 float responsiveAspectRatio = (float) widthPx / heightPx; 640 HotseatSpecsProvider hotseatSpecsProvider = 641 HotseatSpecsProvider.create(new ResourceHelper(context, 642 isTwoPanels ? inv.hotseatSpecsTwoPanelId : inv.hotseatSpecsId)); 643 mResponsiveHotseatSpec = 644 isVerticalBarLayout() ? hotseatSpecsProvider.getCalculatedSpec( 645 responsiveAspectRatio, DimensionType.WIDTH, widthPx) 646 : hotseatSpecsProvider.getCalculatedSpec(responsiveAspectRatio, 647 DimensionType.HEIGHT, heightPx); 648 hotseatQsbSpace = mResponsiveHotseatSpec.getHotseatQsbSpace(); 649 hotseatBarBottomSpace = 650 isVerticalBarLayout() ? 0 : mResponsiveHotseatSpec.getEdgePadding(); 651 mHotseatBarEdgePaddingPx = 652 isVerticalBarLayout() ? mResponsiveHotseatSpec.getEdgePadding() : 0; 653 mHotseatBarWorkspaceSpacePx = 0; 654 655 ResponsiveCellSpecsProvider workspaceCellSpecs = ResponsiveCellSpecsProvider.create( 656 new ResourceHelper(context, 657 isTwoPanels ? inv.workspaceCellSpecsTwoPanelId 658 : inv.workspaceCellSpecsId)); 659 mResponsiveWorkspaceCellSpec = workspaceCellSpecs.getCalculatedSpec( 660 responsiveAspectRatio, heightPx); 661 } else { 662 hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics); 663 hotseatBarBottomSpace = pxFromDp(inv.hotseatBarBottomSpace[mTypeIndex], mMetrics); 664 mHotseatBarEdgePaddingPx = 665 isVerticalBarLayout() ? workspacePageIndicatorHeight : 0; 666 mHotseatBarWorkspaceSpacePx = 667 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding); 668 } 669 670 if (!isVerticalBarLayout()) { 671 // Have a little space between the inset and the QSB 672 if (mInsets.bottom + minQsbMargin > hotseatBarBottomSpace) { 673 int availableSpace = hotseatQsbSpace - (mInsets.bottom - hotseatBarBottomSpace); 674 675 // Only change the spaces if there is space 676 if (availableSpace > 0) { 677 // Make sure there is enough space between hotseat/QSB and QSB/navBar 678 if (availableSpace < minQsbMargin * 2) { 679 minQsbMargin = availableSpace / 2; 680 hotseatQsbSpace = minQsbMargin; 681 } else { 682 hotseatQsbSpace -= minQsbMargin; 683 } 684 } 685 hotseatBarBottomSpacePx = mInsets.bottom + minQsbMargin; 686 687 } else { 688 hotseatBarBottomSpacePx = hotseatBarBottomSpace; 689 } 690 } 691 692 springLoadedHotseatBarTopMarginPx = shouldApplyWidePortraitDimens 693 ? res.getDimensionPixelSize(R.dimen.spring_loaded_hotseat_top_margin_wide_portrait) 694 : res.getDimensionPixelSize(R.dimen.spring_loaded_hotseat_top_margin); 695 696 if (mIsResponsiveGrid) { 697 updateHotseatSizes(mResponsiveWorkspaceCellSpec.getIconSize()); 698 } else { 699 updateHotseatSizes(pxFromDp(inv.iconSize[mTypeIndex], mMetrics)); 700 } 701 702 if (areNavButtonsInline && !isPhone) { 703 inlineNavButtonsEndSpacingPx = 704 res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing); 705 /* 3 nav buttons + Spacing between nav buttons */ 706 navButtonsLayoutWidthPx = 3 * res.getDimensionPixelSize( 707 R.dimen.taskbar_nav_buttons_size) 708 + 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween); 709 /* nav buttons layout width + Space at the end for contextual buttons */ 710 hotseatBarEndOffset = navButtonsLayoutWidthPx + inlineNavButtonsEndSpacingPx; 711 } 712 713 mBubbleBarSpaceThresholdPx = 714 res.getDimensionPixelSize(R.dimen.bubblebar_hotseat_adjustment_threshold); 715 716 // Needs to be calculated after hotseatBarSizePx is correct, 717 // for the available height to be correct 718 if (mIsResponsiveGrid) { 719 int availableResponsiveWidth = 720 availableWidthPx - (isVerticalBarLayout() ? hotseatBarSizePx : 0); 721 int numWorkspaceColumns = getPanelCount() * inv.numColumns; 722 // don't use availableHeightPx because it subtracts mInsets.bottom 723 int availableResponsiveHeight = heightPx - mInsets.top 724 - (isVerticalBarLayout() ? 0 : hotseatBarSizePx); 725 float responsiveAspectRatio = (float) widthPx / heightPx; 726 727 ResponsiveSpecsProvider workspaceSpecs = ResponsiveSpecsProvider.create( 728 new ResourceHelper(context, 729 isTwoPanels ? inv.workspaceSpecsTwoPanelId : inv.workspaceSpecsId), 730 ResponsiveSpecType.Workspace); 731 mResponsiveWorkspaceWidthSpec = workspaceSpecs.getCalculatedSpec(responsiveAspectRatio, 732 DimensionType.WIDTH, numWorkspaceColumns, availableResponsiveWidth); 733 mResponsiveWorkspaceHeightSpec = workspaceSpecs.getCalculatedSpec(responsiveAspectRatio, 734 DimensionType.HEIGHT, inv.numRows, availableResponsiveHeight); 735 736 ResponsiveSpecsProvider allAppsSpecs = ResponsiveSpecsProvider.create( 737 new ResourceHelper(context, 738 isTwoPanels ? inv.allAppsSpecsTwoPanelId : inv.allAppsSpecsId), 739 ResponsiveSpecType.AllApps); 740 mResponsiveAllAppsWidthSpec = allAppsSpecs.getCalculatedSpec(responsiveAspectRatio, 741 DimensionType.WIDTH, numShownAllAppsColumns, availableWidthPx, 742 mResponsiveWorkspaceWidthSpec); 743 mResponsiveAllAppsHeightSpec = allAppsSpecs.getCalculatedSpec(responsiveAspectRatio, 744 DimensionType.HEIGHT, inv.numAllAppsRowsForCellHeightCalculation, 745 heightPx - mInsets.top, mResponsiveWorkspaceHeightSpec); 746 747 ResponsiveSpecsProvider folderSpecs = ResponsiveSpecsProvider.create( 748 new ResourceHelper(context, 749 isTwoPanels ? inv.folderSpecsTwoPanelId : inv.folderSpecsId), 750 ResponsiveSpecType.Folder); 751 mResponsiveFolderWidthSpec = folderSpecs.getCalculatedSpec(responsiveAspectRatio, 752 DimensionType.WIDTH, numFolderColumns, 753 mResponsiveWorkspaceWidthSpec.getAvailableSpace(), 754 mResponsiveWorkspaceWidthSpec); 755 mResponsiveFolderHeightSpec = folderSpecs.getCalculatedSpec(responsiveAspectRatio, 756 DimensionType.HEIGHT, numFolderRows, 757 mResponsiveWorkspaceHeightSpec.getAvailableSpace(), 758 mResponsiveWorkspaceHeightSpec); 759 760 ResponsiveCellSpecsProvider allAppsCellSpecs = ResponsiveCellSpecsProvider.create( 761 new ResourceHelper(context, 762 isTwoPanels ? inv.allAppsCellSpecsTwoPanelId 763 : inv.allAppsCellSpecsId)); 764 mResponsiveAllAppsCellSpec = allAppsCellSpecs.getCalculatedSpec( 765 responsiveAspectRatio, 766 mResponsiveAllAppsHeightSpec.getAvailableSpace(), 767 mResponsiveWorkspaceCellSpec); 768 } 769 770 desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res); 771 desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx; 772 773 overviewTaskMarginPx = res.getDimensionPixelSize(R.dimen.overview_task_margin); 774 overviewTaskIconSizePx = enableOverviewIconMenu() ? res.getDimensionPixelSize( 775 R.dimen.task_thumbnail_icon_menu_drawable_touch_size) : res.getDimensionPixelSize( 776 R.dimen.task_thumbnail_icon_size); 777 overviewTaskIconDrawableSizePx = 778 res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size); 779 overviewTaskIconDrawableSizeGridPx = 780 res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid); 781 overviewTaskThumbnailTopMarginPx = 782 enableOverviewIconMenu() ? 0 : overviewTaskIconSizePx + overviewTaskMarginPx; 783 // Don't add margin with floating search bar to minimize risk of overlapping. 784 overviewActionsTopMarginPx = Flags.floatingSearchBar() ? 0 785 : res.getDimensionPixelSize(R.dimen.overview_actions_top_margin); 786 overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing); 787 overviewActionsButtonSpacing = res.getDimensionPixelSize( 788 R.dimen.overview_actions_button_spacing); 789 overviewActionsHeight = res.getDimensionPixelSize(R.dimen.overview_actions_height); 790 overviewRowSpacing = res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing); 791 overviewGridSideMargin = res.getDimensionPixelSize(R.dimen.overview_grid_side_margin); 792 793 splitPlaceholderInset = res.getDimensionPixelSize(R.dimen.split_placeholder_inset); 794 // We need to use the full window bounds for split determination because on near-square 795 // devices, the available bounds (bounds minus insets) may actually be in landscape while 796 // actually portrait 797 int leftRightSplitPortraitResId = Resources.getSystem().getIdentifier( 798 "config_leftRightSplitInPortrait", "bool", "android"); 799 boolean allowLeftRightSplitInPortrait = 800 leftRightSplitPortraitResId > 0 801 && res.getBoolean(leftRightSplitPortraitResId); 802 if (allowLeftRightSplitInPortrait && isTablet) { 803 isLeftRightSplit = !isLandscape; 804 } else { 805 isLeftRightSplit = isLandscape; 806 } 807 808 // Calculate all of the remaining variables. 809 extraSpace = updateAvailableDimensions(context); 810 811 calculateAndSetWorkspaceVerticalPadding(context, inv, extraSpace); 812 813 int cellLayoutPadding = 814 isTwoPanels ? cellLayoutBorderSpacePx.x / 2 : res.getDimensionPixelSize( 815 R.dimen.cell_layout_padding); 816 cellLayoutPaddingPx = new Rect(cellLayoutPadding, cellLayoutPadding, cellLayoutPadding, 817 cellLayoutPadding); 818 updateWorkspacePadding(); 819 820 // Folder scaling requires correct workspace paddings 821 updateAvailableFolderCellDimensions(res); 822 823 mMinHotseatIconSpacePx = res.getDimensionPixelSize(R.dimen.min_hotseat_icon_space); 824 mMinHotseatQsbWidthPx = res.getDimensionPixelSize(R.dimen.min_hotseat_qsb_width); 825 mMaxHotseatIconSpacePx = areNavButtonsInline 826 ? res.getDimensionPixelSize(R.dimen.max_hotseat_icon_space) : Integer.MAX_VALUE; 827 // Hotseat and QSB width depends on updated cellSize and workspace padding 828 recalculateHotseatWidthAndBorderSpace(); 829 830 if (mIsResponsiveGrid && isVerticalBarLayout()) { 831 hotseatBorderSpace = cellLayoutBorderSpacePx.y; 832 } 833 834 if (shouldShowAllAppsOnSheet()) { 835 allAppsPadding.top = mInsets.top; 836 allAppsShiftRange = heightPx; 837 } else { 838 allAppsPadding.top = 0; 839 allAppsShiftRange = 840 res.getDimensionPixelSize(R.dimen.all_apps_starting_vertical_translate); 841 } 842 allAppsOpenDuration = res.getInteger(R.integer.config_allAppsOpenDuration); 843 allAppsCloseDuration = res.getInteger(R.integer.config_allAppsCloseDuration); 844 845 flingToDeleteThresholdVelocity = res.getDimensionPixelSize( 846 R.dimen.drag_flingToDeleteMinVelocity); 847 848 mViewScaleProvider = viewScaleProvider; 849 850 dimensionOverrideProvider.accept(this); 851 852 // This is done last, after iconSizePx is calculated above. 853 mDotRendererWorkSpace = createDotRenderer(themeManager, iconSizePx, dotRendererCache); 854 mDotRendererAllApps = createDotRenderer(themeManager, allAppsIconSizePx, dotRendererCache); 855 } 856 857 /** 858 * Takes care of the logic that determines if we show a the QSB inline or not. 859 */ isQsbInline(InvariantDeviceProfile inv)860 private boolean isQsbInline(InvariantDeviceProfile inv) { 861 // For foldable (two panel), we inline the qsb if we have the screen open and we are in 862 // either Landscape or Portrait. This cal also be disabled in the device_profile.xml 863 boolean twoPanelCanInline = inv.inlineQsb[INDEX_TWO_PANEL_PORTRAIT] 864 || inv.inlineQsb[INDEX_TWO_PANEL_LANDSCAPE]; 865 866 // In tablets we inline in both orientations but only if we have enough space in the QSB 867 boolean tabletInlineQsb = inv.inlineQsb[INDEX_DEFAULT] || inv.inlineQsb[INDEX_LANDSCAPE]; 868 boolean canQsbInline = isTwoPanels ? twoPanelCanInline : tabletInlineQsb; 869 canQsbInline = canQsbInline && hotseatQsbHeight > 0; 870 871 return (mIsScalableGrid && inv.inlineQsb[mTypeIndex] && canQsbInline) 872 || inv.isFixedLandscape; 873 } 874 createDotRenderer( @onNull ThemeManager themeManager, int size, @NonNull SparseArray<DotRenderer> cache)875 private static DotRenderer createDotRenderer( 876 @NonNull ThemeManager themeManager, int size, @NonNull SparseArray<DotRenderer> cache) { 877 DotRenderer renderer = cache.get(size); 878 if (renderer == null) { 879 renderer = new DotRenderer( 880 size, 881 themeManager.getIconShape().getPath(DEFAULT_DOT_SIZE), 882 DEFAULT_DOT_SIZE); 883 cache.put(size, renderer); 884 } 885 return renderer; 886 } 887 888 /** 889 * Return maximum of all apps row count displayed on screen. Note that 1) Partially displayed 890 * row is counted as 1 row, and 2) we don't exclude the space of floating search bar. This 891 * method is used for calculating number of {@link BubbleTextView} we need to pre-inflate. Thus 892 * reasonable over estimation is fine. 893 */ getMaxAllAppsRowCount()894 public int getMaxAllAppsRowCount() { 895 return (int) (Math.ceil((availableHeightPx - allAppsPadding.top) 896 / (float) allAppsCellHeightPx)); 897 } 898 899 /** 900 * QSB width is always calculated because when in 3 button nav the width doesn't follow the 901 * width of the hotseat. 902 */ calculateQsbWidth(int hotseatBorderSpace)903 private int calculateQsbWidth(int hotseatBorderSpace) { 904 int iconExtraSpacePx = iconSizePx - getIconVisibleSizePx(iconSizePx); 905 if (isQsbInline) { 906 int columns = getPanelCount() * inv.numColumns; 907 return getIconToIconWidthForColumns(columns) 908 - iconSizePx * numShownHotseatIcons 909 - hotseatBorderSpace * numShownHotseatIcons 910 - iconExtraSpacePx; 911 } else { 912 return getIconToIconWidthForColumns(mHotseatColumnSpan) - iconExtraSpacePx; 913 } 914 } 915 getIconToIconWidthForColumns(int columns)916 private int getIconToIconWidthForColumns(int columns) { 917 return columns * getCellSize().x 918 + (columns - 1) * cellLayoutBorderSpacePx.x 919 - getCellHorizontalSpace(); 920 } 921 getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res)922 private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) { 923 if (mIsResponsiveGrid) { 924 return mResponsiveWorkspaceWidthSpec.getStartPaddingPx(); 925 } 926 927 if (isVerticalBarLayout()) { 928 return 0; 929 } 930 931 return mIsScalableGrid 932 ? pxFromDp(idp.horizontalMargin[mTypeIndex], mMetrics) 933 : res.getDimensionPixelSize(R.dimen.dynamic_grid_left_right_margin); 934 } 935 calculateAndSetWorkspaceVerticalPadding(Context context, InvariantDeviceProfile inv, int extraSpace)936 private void calculateAndSetWorkspaceVerticalPadding(Context context, 937 InvariantDeviceProfile inv, 938 int extraSpace) { 939 if (mIsResponsiveGrid) { 940 workspaceTopPadding = mResponsiveWorkspaceHeightSpec.getStartPaddingPx(); 941 workspaceBottomPadding = mResponsiveWorkspaceHeightSpec.getEndPaddingPx(); 942 } else if (mIsScalableGrid && inv.devicePaddingId != INVALID_RESOURCE_HANDLE) { 943 // Paddings were created assuming no scaling, so we first unscale the extra space. 944 int unscaledExtraSpace = (int) (extraSpace / cellScaleToFit); 945 DevicePaddings devicePaddings = new DevicePaddings(context, inv.devicePaddingId); 946 DevicePadding padding = devicePaddings.getDevicePadding(unscaledExtraSpace); 947 maxEmptySpace = padding.getMaxEmptySpacePx(); 948 949 int paddingWorkspaceTop = padding.getWorkspaceTopPadding(unscaledExtraSpace); 950 int paddingWorkspaceBottom = padding.getWorkspaceBottomPadding(unscaledExtraSpace); 951 952 workspaceTopPadding = Math.round(paddingWorkspaceTop * cellScaleToFit); 953 workspaceBottomPadding = Math.round(paddingWorkspaceBottom * cellScaleToFit); 954 } 955 } 956 957 /** Updates hotseatCellHeightPx and hotseatBarSizePx */ updateHotseatSizes(int hotseatIconSizePx)958 private void updateHotseatSizes(int hotseatIconSizePx) { 959 // Ensure there is enough space for folder icons, which have a slightly larger radius. 960 hotseatCellHeightPx = getIconSizeWithOverlap(hotseatIconSizePx); 961 962 if (isVerticalBarLayout()) { 963 hotseatBarSizePx = hotseatIconSizePx + mHotseatBarEdgePaddingPx 964 + mHotseatBarWorkspaceSpacePx; 965 } else if (isQsbInline) { 966 hotseatBarSizePx = Math.max(hotseatIconSizePx, hotseatQsbVisualHeight) 967 + hotseatBarBottomSpacePx; 968 } else { 969 hotseatBarSizePx = hotseatIconSizePx 970 + hotseatQsbSpace 971 + hotseatQsbVisualHeight 972 + hotseatBarBottomSpacePx; 973 } 974 } 975 976 /** 977 * Calculates the width of the hotseat, changing spaces between the icons and removing icons if 978 * necessary. 979 */ recalculateHotseatWidthAndBorderSpace()980 public void recalculateHotseatWidthAndBorderSpace() { 981 if (!mIsScalableGrid) return; 982 983 updateHotseatWidthAndBorderSpace(inv.numColumns); 984 int numWorkspaceColumns = getPanelCount() * inv.numColumns; 985 if (isTwoPanels) { 986 updateHotseatWidthAndBorderSpace(inv.numDatabaseHotseatIcons); 987 // If hotseat doesn't fit with current width, increase column span to fit by multiple 988 // of 2. 989 while (hotseatBorderSpace < mMinHotseatIconSpacePx 990 && mHotseatColumnSpan < numWorkspaceColumns) { 991 updateHotseatWidthAndBorderSpace(mHotseatColumnSpan + 2); 992 } 993 } 994 if (isQsbInline) { 995 // If QSB is inline, reduce column span until it fits. 996 int maxHotseatWidthAllowedPx = getIconToIconWidthForColumns(numWorkspaceColumns); 997 int minHotseatWidthRequiredPx = 998 mMinHotseatQsbWidthPx + hotseatBorderSpace + mHotseatWidthPx; 999 while (minHotseatWidthRequiredPx > maxHotseatWidthAllowedPx 1000 && mHotseatColumnSpan > 1) { 1001 updateHotseatWidthAndBorderSpace(mHotseatColumnSpan - 1); 1002 minHotseatWidthRequiredPx = 1003 mMinHotseatQsbWidthPx + hotseatBorderSpace + mHotseatWidthPx; 1004 } 1005 } 1006 hotseatQsbWidth = calculateQsbWidth(hotseatBorderSpace); 1007 1008 // Spaces should be correct when the nav buttons are not inline 1009 if (!areNavButtonsInline) { 1010 return; 1011 } 1012 1013 // The side space with inline buttons should be what is defined in InvariantDeviceProfile 1014 int sideSpacePx = inlineNavButtonsEndSpacingPx; 1015 int maxHotseatWidthPx = availableWidthPx - sideSpacePx - hotseatBarEndOffset; 1016 int maxHotseatIconsWidthPx = maxHotseatWidthPx - (isQsbInline ? hotseatQsbWidth : 0); 1017 hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx, 1018 (isQsbInline ? 1 : 0) + /* border between nav buttons and first icon */ 1); 1019 1020 if (hotseatBorderSpace >= mMinHotseatIconSpacePx) { 1021 return; 1022 } 1023 1024 // Border space can't be less than the minimum 1025 hotseatBorderSpace = mMinHotseatIconSpacePx; 1026 int requiredWidth = getHotseatRequiredWidth(); 1027 1028 // If there is an inline qsb, change its size 1029 if (isQsbInline) { 1030 hotseatQsbWidth -= requiredWidth - maxHotseatWidthPx; 1031 if (hotseatQsbWidth >= mMinHotseatQsbWidthPx) { 1032 return; 1033 } 1034 1035 // QSB can't be less than the minimum 1036 hotseatQsbWidth = mMinHotseatQsbWidthPx; 1037 } 1038 1039 maxHotseatIconsWidthPx = maxHotseatWidthPx - (isQsbInline ? hotseatQsbWidth : 0); 1040 1041 // If it still doesn't fit, start removing icons 1042 do { 1043 numShownHotseatIcons--; 1044 hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx, 1045 (isQsbInline ? 1 : 0) + /* border between nav buttons and first icon */ 1); 1046 } while (hotseatBorderSpace < mMinHotseatIconSpacePx && numShownHotseatIcons > 1); 1047 } 1048 updateHotseatWidthAndBorderSpace(int columns)1049 private void updateHotseatWidthAndBorderSpace(int columns) { 1050 mHotseatColumnSpan = columns; 1051 mHotseatWidthPx = getIconToIconWidthForColumns(mHotseatColumnSpan); 1052 hotseatBorderSpace = calculateHotseatBorderSpace(mHotseatWidthPx, /* numExtraBorder= */ 0); 1053 } 1054 getCellLayoutBorderSpace(InvariantDeviceProfile idp)1055 private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) { 1056 return getCellLayoutBorderSpace(idp, 1f); 1057 } 1058 getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale)1059 private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale) { 1060 int horizontalSpacePx = 0; 1061 int verticalSpacePx = 0; 1062 1063 if (mIsResponsiveGrid) { 1064 horizontalSpacePx = mResponsiveWorkspaceWidthSpec.getGutterPx(); 1065 verticalSpacePx = mResponsiveWorkspaceHeightSpec.getGutterPx(); 1066 } else if (mIsScalableGrid) { 1067 horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics, scale); 1068 verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics, scale); 1069 } 1070 1071 return new Point(horizontalSpacePx, verticalSpacePx); 1072 } 1073 getDisplayInfo()1074 public Info getDisplayInfo() { 1075 return mInfo; 1076 } 1077 1078 @VisibleForTesting getHotseatColumnSpan()1079 public int getHotseatColumnSpan() { 1080 return mHotseatColumnSpan; 1081 } 1082 1083 @VisibleForTesting getHotseatWidthPx()1084 public int getHotseatWidthPx() { 1085 return mHotseatWidthPx; 1086 } 1087 toBuilder(Context context)1088 public Builder toBuilder(Context context) { 1089 WindowBounds bounds = new WindowBounds( 1090 widthPx, heightPx, availableWidthPx, availableHeightPx, rotationHint); 1091 bounds.bounds.offsetTo(windowX, windowY); 1092 bounds.insets.set(mInsets); 1093 1094 SparseArray<DotRenderer> dotRendererCache = new SparseArray<>(); 1095 dotRendererCache.put(iconSizePx, mDotRendererWorkSpace); 1096 dotRendererCache.put(allAppsIconSizePx, mDotRendererAllApps); 1097 1098 return inv.newDPBuilder(context, mInfo) 1099 .setWindowBounds(bounds) 1100 .setIsMultiDisplay(isMultiDisplay) 1101 .setMultiWindowMode(isMultiWindowMode) 1102 .setDotRendererCache(dotRendererCache) 1103 .setGestureMode(isGestureMode); 1104 } 1105 copy(Context context)1106 public DeviceProfile copy(Context context) { 1107 return toBuilder(context).build(); 1108 } 1109 1110 /** 1111 * TODO: Move this to the builder as part of setMultiWindowMode 1112 */ getMultiWindowProfile(Context context, WindowBounds windowBounds)1113 public DeviceProfile getMultiWindowProfile(Context context, WindowBounds windowBounds) { 1114 DeviceProfile profile = toBuilder(context) 1115 .setWindowBounds(windowBounds) 1116 .setMultiWindowMode(true) 1117 .build(); 1118 1119 // We use these scales to measure and layout the widgets using their full invariant profile 1120 // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans. 1121 float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x; 1122 float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y; 1123 if (appWidgetScaleX != 1 || appWidgetScaleY != 1) { 1124 final PointF p = new PointF(appWidgetScaleX, appWidgetScaleY); 1125 profile = profile.toBuilder(context) 1126 .setViewScaleProvider(i -> p) 1127 .build(); 1128 } 1129 1130 profile.hideWorkspaceLabelsIfNotEnoughSpace(); 1131 1132 return profile; 1133 } 1134 1135 /** 1136 * Checks if there is enough space for labels on the workspace. 1137 * If there is not, labels on the Workspace are hidden. 1138 * It is important to call this method after the All Apps variables have been set. 1139 */ hideWorkspaceLabelsIfNotEnoughSpace()1140 private void hideWorkspaceLabelsIfNotEnoughSpace() { 1141 float iconTextHeight = Utilities.calculateTextHeight(iconTextSizePx); 1142 float workspaceCellPaddingY = getCellSize().y - iconSizePx - iconDrawablePaddingPx 1143 - iconTextHeight; 1144 1145 // We want enough space so that the text is closer to its corresponding icon. 1146 if (workspaceCellPaddingY < iconTextHeight) { 1147 iconTextSizePx = 0; 1148 iconDrawablePaddingPx = 0; 1149 cellHeightPx = getIconSizeWithOverlap(iconSizePx); 1150 autoResizeAllAppsCells(); 1151 } 1152 } 1153 1154 /** 1155 * Returns the amount of extra (or unused) vertical space. 1156 */ updateAvailableDimensions(Context context)1157 private int updateAvailableDimensions(Context context) { 1158 iconCenterVertically = (mIsScalableGrid || mIsResponsiveGrid) && isVerticalBarLayout(); 1159 1160 if (mIsResponsiveGrid) { 1161 iconSizePx = mResponsiveWorkspaceCellSpec.getIconSize(); 1162 iconTextSizePx = mResponsiveWorkspaceCellSpec.getIconTextSize(); 1163 mIconDrawablePaddingOriginalPx = mResponsiveWorkspaceCellSpec.getIconDrawablePadding(); 1164 updateIconSize(1f, context); 1165 updateWorkspacePadding(); 1166 return 0; 1167 } 1168 1169 float invIconSizeDp = inv.iconSize[mTypeIndex]; 1170 float invIconTextSizeSp = inv.iconTextSize[mTypeIndex]; 1171 iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics)); 1172 iconTextSizePx = pxFromSp(invIconTextSizeSp, mMetrics); 1173 1174 updateIconSize(1f, context); 1175 updateWorkspacePadding(); 1176 1177 // Check to see if the icons fit within the available height. 1178 float usedHeight = getCellLayoutHeightSpecification(); 1179 final int maxHeight = getCellLayoutHeight(); 1180 float extraHeight = Math.max(0, maxHeight - usedHeight); 1181 float scaleY = maxHeight / usedHeight; 1182 boolean shouldScale = scaleY < 1f; 1183 1184 float scaleX = 1f; 1185 if (mIsScalableGrid) { 1186 // We scale to fit the cellWidth and cellHeight in the available space. 1187 // The benefit of scalable grids is that we can get consistent aspect ratios between 1188 // devices. 1189 float usedWidth = 1190 getCellLayoutWidthSpecification() + (desiredWorkspaceHorizontalMarginPx * 2); 1191 // We do not subtract padding here, as we also scale the workspace padding if needed. 1192 scaleX = availableWidthPx / usedWidth; 1193 shouldScale = true; 1194 } 1195 1196 if (shouldScale) { 1197 float scale = Math.min(scaleX, scaleY); 1198 updateIconSize(scale, context); 1199 extraHeight = Math.max(0, maxHeight - getCellLayoutHeightSpecification()); 1200 } 1201 1202 return Math.round(extraHeight); 1203 } 1204 1205 private int getCellLayoutHeightSpecification() { 1206 return (cellHeightPx * inv.numRows) + (cellLayoutBorderSpacePx.y * (inv.numRows - 1)) 1207 + cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom; 1208 } 1209 1210 private int getCellLayoutWidthSpecification() { 1211 int numColumns = getPanelCount() * inv.numColumns; 1212 return (cellWidthPx * numColumns) + (cellLayoutBorderSpacePx.x * (numColumns - 1)) 1213 + cellLayoutPaddingPx.left + cellLayoutPaddingPx.right; 1214 } 1215 1216 private int getNormalizedIconDrawablePadding(int iconSizePx, int iconDrawablePadding) { 1217 return Math.max(0, iconDrawablePadding 1218 - ((iconSizePx - getIconVisibleSizePx(iconSizePx)) / 2)); 1219 } 1220 1221 private int getNormalizedIconDrawablePadding() { 1222 return getNormalizedIconDrawablePadding(iconSizePx, mIconDrawablePaddingOriginalPx); 1223 } 1224 1225 private int getNormalizedFolderChildDrawablePaddingPx(int textHeight) { 1226 // TODO(b/235886078): workaround needed because of this bug 1227 // Icons are 10% larger on XML than their visual size, 1228 // so remove that extra space to get labels closer to the correct padding 1229 int drawablePadding = (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3; 1230 1231 int iconSizeDiff = folderChildIconSizePx - getIconVisibleSizePx(folderChildIconSizePx); 1232 return Math.max(0, drawablePadding - iconSizeDiff / 2); 1233 } 1234 1235 private int getIconSizeWithOverlap(int iconSize) { 1236 return (int) Math.ceil(iconSize * ClippedFolderIconLayoutRule.getIconOverlapFactor()); 1237 } 1238 1239 /** 1240 * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx, 1241 * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants, 1242 * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx. 1243 */ 1244 public void updateIconSize(float scale, Context context) { 1245 // Icon scale should never exceed 1, otherwise pixellation may occur. 1246 iconScale = Math.min(1f, scale); 1247 cellScaleToFit = scale; 1248 1249 // Workspace 1250 final boolean isVerticalLayout = isVerticalBarLayout(); 1251 cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv, scale); 1252 1253 if (mIsResponsiveGrid) { 1254 cellWidthPx = mResponsiveWorkspaceWidthSpec.getCellSizePx(); 1255 cellHeightPx = mResponsiveWorkspaceHeightSpec.getCellSizePx(); 1256 1257 if (cellWidthPx < iconSizePx) { 1258 // get a smaller icon size 1259 iconSizePx = mIconSizeSteps.getIconSmallerThan(cellWidthPx); 1260 } 1261 1262 if (isVerticalLayout) { 1263 iconDrawablePaddingPx = 0; 1264 iconTextSizePx = 0; 1265 } else { 1266 iconDrawablePaddingPx = getNormalizedIconDrawablePadding(); 1267 } 1268 1269 CellContentDimensions cellContentDimensions = new CellContentDimensions(iconSizePx, 1270 iconDrawablePaddingPx, 1271 iconTextSizePx); 1272 int cellContentHeight = cellContentDimensions.resizeToFitCellHeight(cellHeightPx, 1273 mIconSizeSteps); 1274 iconSizePx = cellContentDimensions.getIconSizePx(); 1275 iconDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx(); 1276 iconTextSizePx = cellContentDimensions.getIconTextSizePx(); 1277 1278 if (isVerticalLayout) { 1279 cellYPaddingPx = Math.max(0, getCellSize().y - getIconSizeWithOverlap(iconSizePx)) 1280 / 2; 1281 } else { 1282 cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2; 1283 } 1284 } else if (mIsScalableGrid) { 1285 iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale); 1286 cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale); 1287 cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale); 1288 1289 if (cellWidthPx < iconSizePx) { 1290 // If cellWidth no longer fit iconSize, reduce borderSpace to make cellWidth bigger. 1291 int numColumns = getPanelCount() * inv.numColumns; 1292 int numBorders = numColumns - 1; 1293 int extraWidthRequired = (iconSizePx - cellWidthPx) * numColumns; 1294 if (cellLayoutBorderSpacePx.x * numBorders >= extraWidthRequired) { 1295 cellWidthPx = iconSizePx; 1296 cellLayoutBorderSpacePx.x -= extraWidthRequired / numBorders; 1297 } else { 1298 // If it still doesn't fit, set borderSpace to 0 and distribute the space for 1299 // cellWidth, and reduce iconSize. 1300 cellWidthPx = (cellWidthPx * numColumns 1301 + cellLayoutBorderSpacePx.x * numBorders) / numColumns; 1302 iconSizePx = Math.min(iconSizePx, cellWidthPx); 1303 cellLayoutBorderSpacePx.x = 0; 1304 } 1305 } 1306 1307 int cellTextAndPaddingHeight = 1308 iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx); 1309 int cellContentHeight = iconSizePx + cellTextAndPaddingHeight; 1310 if (cellHeightPx < cellContentHeight) { 1311 // If cellHeight no longer fit iconSize, reduce borderSpace to make cellHeight 1312 // bigger. 1313 int numBorders = inv.numRows - 1; 1314 int extraHeightRequired = (cellContentHeight - cellHeightPx) * inv.numRows; 1315 if (cellLayoutBorderSpacePx.y * numBorders >= extraHeightRequired) { 1316 cellHeightPx = cellContentHeight; 1317 cellLayoutBorderSpacePx.y -= extraHeightRequired / numBorders; 1318 } else { 1319 // If it still doesn't fit, set borderSpace to 0 to recover space. 1320 cellHeightPx = (cellHeightPx * inv.numRows 1321 + cellLayoutBorderSpacePx.y * numBorders) / inv.numRows; 1322 cellLayoutBorderSpacePx.y = 0; 1323 // Reduce iconDrawablePaddingPx to make cellContentHeight smaller. 1324 int cellContentWithoutPadding = cellContentHeight - iconDrawablePaddingPx; 1325 if (cellContentWithoutPadding <= cellHeightPx) { 1326 iconDrawablePaddingPx = cellContentHeight - cellHeightPx; 1327 } else { 1328 // If it still doesn't fit, set iconDrawablePaddingPx to 0 to recover space, 1329 // then proportional reduce iconSizePx and iconTextSizePx to fit. 1330 iconDrawablePaddingPx = 0; 1331 float ratio = cellHeightPx / (float) cellContentWithoutPadding; 1332 iconSizePx = (int) (iconSizePx * ratio); 1333 iconTextSizePx = (int) (iconTextSizePx * ratio); 1334 } 1335 cellTextAndPaddingHeight = 1336 iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx); 1337 } 1338 cellContentHeight = iconSizePx + cellTextAndPaddingHeight; 1339 } 1340 cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2; 1341 desiredWorkspaceHorizontalMarginPx = 1342 (int) (desiredWorkspaceHorizontalMarginOriginalPx * scale); 1343 } else { 1344 iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale); 1345 cellWidthPx = iconSizePx + iconDrawablePaddingPx; 1346 cellHeightPx = getIconSizeWithOverlap(iconSizePx) 1347 + iconDrawablePaddingPx 1348 + Utilities.calculateTextHeight(iconTextSizePx); 1349 int cellPaddingY = (getCellSize().y - cellHeightPx) / 2; 1350 if (iconDrawablePaddingPx > cellPaddingY && !isVerticalLayout 1351 && !isMultiWindowMode) { 1352 // Ensures that the label is closer to its corresponding icon. This is not an issue 1353 // with vertical bar layout or multi-window mode since the issue is handled 1354 // separately with their calls to {@link #adjustToHideWorkspaceLabels}. 1355 cellHeightPx -= (iconDrawablePaddingPx - cellPaddingY); 1356 iconDrawablePaddingPx = cellPaddingY; 1357 } 1358 } 1359 1360 // All apps 1361 if (mIsResponsiveGrid) { 1362 updateAllAppsWithResponsiveMeasures(); 1363 } else { 1364 updateAllAppsIconSize(scale, context.getResources()); 1365 } 1366 updateAllAppsContainerWidth(); 1367 if (isVerticalLayout && !mIsResponsiveGrid) { 1368 hideWorkspaceLabelsIfNotEnoughSpace(); 1369 } 1370 if (inv.enableTwoLinesInAllApps) { 1371 // Add extra textHeight to the existing allAppsCellHeight. 1372 allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx); 1373 } 1374 1375 updateHotseatSizes(iconSizePx); 1376 1377 // Folder icon 1378 folderIconSizePx = Math.round(iconSizePx * ICON_VISIBLE_AREA_FACTOR); 1379 folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2; 1380 1381 // Update widget padding: 1382 float minSpacing = pxFromDp(MIN_WIDGET_PADDING_DP, mMetrics); 1383 if (cellLayoutBorderSpacePx.x < minSpacing 1384 || cellLayoutBorderSpacePx.y < minSpacing) { 1385 widgetPadding.left = widgetPadding.right = 1386 Math.round(Math.max(0, minSpacing - cellLayoutBorderSpacePx.x)); 1387 widgetPadding.top = widgetPadding.bottom = 1388 Math.round(Math.max(0, minSpacing - cellLayoutBorderSpacePx.y)); 1389 } else { 1390 widgetPadding.setEmpty(); 1391 } 1392 } 1393 1394 /** 1395 * This method calculates the space between the icons to achieve a certain width. 1396 */ 1397 private int calculateHotseatBorderSpace(float hotseatWidthPx, int numExtraBorder) { 1398 int numBorders = (numShownHotseatIcons - 1 + numExtraBorder); 1399 if (numBorders <= 0) return 0; 1400 1401 float hotseatIconsTotalPx = iconSizePx * numShownHotseatIcons; 1402 int hotseatBorderSpacePx = (int) (hotseatWidthPx - hotseatIconsTotalPx) / numBorders; 1403 return Math.min(hotseatBorderSpacePx, mMaxHotseatIconSpacePx); 1404 } 1405 1406 /** 1407 * Updates the iconSize for allApps* variants. 1408 */ 1409 private void updateAllAppsIconSize(float scale, Resources res) { 1410 allAppsBorderSpacePx = new Point( 1411 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics, scale), 1412 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics, scale)); 1413 // AllApps cells don't have real space between cells, 1414 // so we add the border space to the cell height 1415 allAppsCellHeightPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].y, mMetrics) 1416 + allAppsBorderSpacePx.y; 1417 // but width is just the cell, 1418 // the border is added in #updateAllAppsContainerWidth 1419 if (mIsScalableGrid) { 1420 allAppsIconSizePx = pxFromDp(inv.allAppsIconSize[mTypeIndex], mMetrics); 1421 allAppsIconTextSizePx = pxFromSp(inv.allAppsIconTextSize[mTypeIndex], mMetrics); 1422 allAppsIconDrawablePaddingPx = getNormalizedIconDrawablePadding(); 1423 allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics, scale); 1424 1425 if (allAppsCellWidthPx < allAppsIconSizePx) { 1426 // If allAppsCellWidth no longer fit allAppsIconSize, reduce allAppsBorderSpace to 1427 // make allAppsCellWidth bigger. 1428 int numBorders = inv.numAllAppsColumns - 1; 1429 int extraWidthRequired = 1430 (allAppsIconSizePx - allAppsCellWidthPx) * inv.numAllAppsColumns; 1431 if (allAppsBorderSpacePx.x * numBorders >= extraWidthRequired) { 1432 allAppsCellWidthPx = allAppsIconSizePx; 1433 allAppsBorderSpacePx.x -= extraWidthRequired / numBorders; 1434 } else { 1435 // If it still doesn't fit, set allAppsBorderSpace to 0 and distribute the space 1436 // for allAppsCellWidth, and reduce allAppsIconSize. 1437 allAppsCellWidthPx = (allAppsCellWidthPx * inv.numAllAppsColumns 1438 + allAppsBorderSpacePx.x * numBorders) / inv.numAllAppsColumns; 1439 allAppsIconSizePx = Math.min(allAppsIconSizePx, allAppsCellWidthPx); 1440 allAppsBorderSpacePx.x = 0; 1441 } 1442 } 1443 1444 int cellContentHeight = allAppsIconSizePx 1445 + Utilities.calculateTextHeight(allAppsIconTextSizePx) + allAppsBorderSpacePx.y; 1446 if (allAppsCellHeightPx < cellContentHeight) { 1447 // Increase allAppsCellHeight to fit its content. 1448 allAppsCellHeightPx = cellContentHeight; 1449 } 1450 } else { 1451 float invIconSizeDp = inv.allAppsIconSize[mTypeIndex]; 1452 float invIconTextSizeSp = inv.allAppsIconTextSize[mTypeIndex]; 1453 allAppsIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale)); 1454 allAppsIconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * scale); 1455 allAppsIconDrawablePaddingPx = 1456 res.getDimensionPixelSize(R.dimen.all_apps_icon_drawable_padding); 1457 allAppsCellWidthPx = allAppsIconSizePx + (2 * allAppsIconDrawablePaddingPx); 1458 } 1459 } 1460 1461 private void updateAllAppsWithResponsiveMeasures() { 1462 allAppsIconSizePx = mResponsiveAllAppsCellSpec.getIconSize(); 1463 allAppsIconTextSizePx = mResponsiveAllAppsCellSpec.getIconTextSize(); 1464 allAppsIconDrawablePaddingPx = getNormalizedIconDrawablePadding(allAppsIconSizePx, 1465 mResponsiveAllAppsCellSpec.getIconDrawablePadding()); 1466 allAppsBorderSpacePx = new Point( 1467 mResponsiveAllAppsWidthSpec.getGutterPx(), 1468 mResponsiveAllAppsHeightSpec.getGutterPx() 1469 ); 1470 allAppsCellHeightPx = mResponsiveAllAppsHeightSpec.getCellSizePx(); 1471 allAppsCellWidthPx = mResponsiveAllAppsWidthSpec.getCellSizePx(); 1472 1473 // This workaround is needed to align AllApps icons with Workspace icons 1474 // since AllApps doesn't have borders between cells 1475 int halfBorder = allAppsBorderSpacePx.x / 2; 1476 allAppsPadding.left = mResponsiveAllAppsWidthSpec.getStartPaddingPx() - halfBorder; 1477 allAppsPadding.right = mResponsiveAllAppsWidthSpec.getEndPaddingPx() - halfBorder; 1478 1479 1480 // Reduce the size of the app icon if it doesn't fit 1481 if (allAppsCellWidthPx < allAppsIconSizePx) { 1482 // get a smaller icon size 1483 allAppsIconSizePx = mIconSizeSteps.getIconSmallerThan(allAppsCellWidthPx); 1484 } 1485 1486 CellContentDimensions cellContentDimensions = new CellContentDimensions( 1487 allAppsIconSizePx, allAppsIconDrawablePaddingPx, (int) allAppsIconTextSizePx); 1488 1489 if (allAppsCellHeightPx < cellContentDimensions.getCellContentHeight()) { 1490 if (isVerticalBarLayout()) { 1491 if (allAppsCellHeightPx < allAppsIconSizePx) { 1492 cellContentDimensions.setIconSizePx( 1493 mIconSizeSteps.getIconSmallerThan(allAppsCellHeightPx)); 1494 } 1495 } else { 1496 cellContentDimensions.resizeToFitCellHeight(allAppsCellHeightPx, 1497 mIconSizeSteps); 1498 } 1499 allAppsIconSizePx = cellContentDimensions.getIconSizePx(); 1500 allAppsIconDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx(); 1501 allAppsIconTextSizePx = cellContentDimensions.getIconTextSizePx(); 1502 } 1503 1504 allAppsCellHeightPx += mResponsiveAllAppsHeightSpec.getGutterPx(); 1505 1506 if (isVerticalBarLayout()) { 1507 autoResizeAllAppsCells(); 1508 } 1509 } 1510 1511 /** 1512 * Re-computes the all-apps cell size to be independent of workspace 1513 */ 1514 public void autoResizeAllAppsCells() { 1515 int textHeight = Utilities.calculateTextHeight(allAppsIconTextSizePx); 1516 int topBottomPadding = textHeight; 1517 allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx 1518 + textHeight + (topBottomPadding * 2); 1519 } 1520 1521 private void updateAllAppsContainerWidth() { 1522 int cellLayoutHorizontalPadding = 1523 (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right) / 2; 1524 if (isTablet) { 1525 int usedWidth = (allAppsCellWidthPx * numShownAllAppsColumns) 1526 + (allAppsBorderSpacePx.x * (numShownAllAppsColumns - 1)) 1527 + allAppsPadding.left + allAppsPadding.right; 1528 allAppsLeftRightMargin = Math.max(1, (availableWidthPx - usedWidth) / 2); 1529 } else if (!mIsResponsiveGrid) { 1530 allAppsPadding.left = allAppsPadding.right = 1531 Math.max(0, desiredWorkspaceHorizontalMarginPx + cellLayoutHorizontalPadding 1532 - (allAppsBorderSpacePx.x / 2)); 1533 } 1534 } 1535 1536 /** Whether All Apps should be presented on a bottom sheet. */ 1537 public boolean shouldShowAllAppsOnSheet() { 1538 return isTablet || Flags.allAppsSheetForHandheld(); 1539 } 1540 1541 private void setupAllAppsStyle(Context context) { 1542 TypedArray allAppsStyle = context.obtainStyledAttributes( 1543 inv.allAppsStyle != INVALID_RESOURCE_HANDLE ? inv.allAppsStyle 1544 : R.style.AllAppsStyleDefault, R.styleable.AllAppsStyle); 1545 1546 allAppsPadding.left = allAppsPadding.right = allAppsStyle.getDimensionPixelSize( 1547 R.styleable.AllAppsStyle_horizontalPadding, 0); 1548 allAppsStyle.recycle(); 1549 } 1550 1551 private void updateAvailableFolderCellDimensions(Resources res) { 1552 updateFolderCellSize(1f, res); 1553 1554 // Responsive grid doesn't need to scale the folder 1555 if (mIsResponsiveGrid) return; 1556 1557 // For usability we can't have the folder use the whole width of the screen 1558 Point totalWorkspacePadding = getTotalWorkspacePadding(); 1559 1560 // Check if the folder fit within the available height. 1561 float contentUsedHeight = folderCellHeightPx * numFolderRows 1562 + ((numFolderRows - 1) * folderCellLayoutBorderSpacePx.y) 1563 + folderFooterHeightPx 1564 + folderContentPaddingTop; 1565 int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y; 1566 float scaleY = contentMaxHeight / contentUsedHeight; 1567 1568 // Check if the folder fit within the available width. 1569 float contentUsedWidth = folderCellWidthPx * numFolderColumns 1570 + ((numFolderColumns - 1) * folderCellLayoutBorderSpacePx.x) 1571 + folderContentPaddingLeftRight * 2; 1572 int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x; 1573 float scaleX = contentMaxWidth / contentUsedWidth; 1574 1575 float scale = Math.min(scaleX, scaleY); 1576 if (scale < 1f) { 1577 updateFolderCellSize(scale, res); 1578 } 1579 } 1580 1581 private void updateFolderCellSize(float scale, Resources res) { 1582 int minLabelTextSize = pxFromSp(MIN_FOLDER_TEXT_SIZE_SP, mMetrics, scale); 1583 if (mIsResponsiveGrid) { 1584 folderChildIconSizePx = mResponsiveWorkspaceCellSpec.getIconSize(); 1585 folderChildTextSizePx = mResponsiveWorkspaceCellSpec.getIconTextSize(); 1586 folderLabelTextSizePx = Math.max(minLabelTextSize, 1587 (int) (folderChildTextSizePx * folderLabelTextScale)); 1588 int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx); 1589 1590 folderCellWidthPx = mResponsiveFolderWidthSpec.getCellSizePx(); 1591 folderCellHeightPx = mResponsiveFolderHeightSpec.getCellSizePx(); 1592 folderContentPaddingTop = mResponsiveFolderHeightSpec.getStartPaddingPx(); 1593 folderFooterHeightPx = mResponsiveFolderHeightSpec.getEndPaddingPx(); 1594 1595 folderCellLayoutBorderSpacePx = new Point(mResponsiveFolderWidthSpec.getGutterPx(), 1596 mResponsiveFolderHeightSpec.getGutterPx()); 1597 1598 folderContentPaddingLeftRight = mResponsiveFolderWidthSpec.getStartPaddingPx(); 1599 1600 // Reduce icon width if it's wider than the expected folder cell width 1601 if (folderCellWidthPx < folderChildIconSizePx) { 1602 folderChildIconSizePx = mIconSizeSteps.getIconSmallerThan(folderCellWidthPx); 1603 } 1604 1605 // Recalculating padding and cell height 1606 folderChildDrawablePaddingPx = mResponsiveWorkspaceCellSpec.getIconDrawablePadding(); 1607 1608 CellContentDimensions cellContentDimensions = new CellContentDimensions( 1609 folderChildIconSizePx, 1610 folderChildDrawablePaddingPx, 1611 folderChildTextSizePx); 1612 cellContentDimensions.resizeToFitCellHeight(folderCellHeightPx, mIconSizeSteps); 1613 folderChildIconSizePx = cellContentDimensions.getIconSizePx(); 1614 folderChildDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx(); 1615 folderChildTextSizePx = cellContentDimensions.getIconTextSizePx(); 1616 folderLabelTextSizePx = Math.max(minLabelTextSize, 1617 (int) (folderChildTextSizePx * folderLabelTextScale)); 1618 return; 1619 } 1620 1621 float invIconSizeDp = inv.iconSize[mTypeIndex]; 1622 float invIconTextSizeDp = inv.iconTextSize[mTypeIndex]; 1623 folderChildIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale)); 1624 folderChildTextSizePx = pxFromSp(invIconTextSizeDp, mMetrics, scale); 1625 folderLabelTextSizePx = Math.max(minLabelTextSize, 1626 (int) (folderChildTextSizePx * folderLabelTextScale)); 1627 int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx); 1628 1629 if (mIsScalableGrid) { 1630 if (inv.folderStyle == INVALID_RESOURCE_HANDLE) { 1631 folderCellWidthPx = roundPxValueFromFloat(getCellSize().x * scale); 1632 folderCellHeightPx = roundPxValueFromFloat(getCellSize().y * scale); 1633 } else { 1634 folderCellWidthPx = roundPxValueFromFloat(folderCellWidthPx * scale); 1635 folderCellHeightPx = roundPxValueFromFloat(folderCellHeightPx * scale); 1636 } 1637 // Recalculating padding and cell height 1638 folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight); 1639 1640 CellContentDimensions cellContentDimensions = new CellContentDimensions( 1641 folderChildIconSizePx, 1642 folderChildDrawablePaddingPx, 1643 folderChildTextSizePx); 1644 cellContentDimensions.resizeToFitCellHeight(folderCellHeightPx, mIconSizeSteps); 1645 folderChildIconSizePx = cellContentDimensions.getIconSizePx(); 1646 folderChildDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx(); 1647 folderChildTextSizePx = cellContentDimensions.getIconTextSizePx(); 1648 1649 folderContentPaddingTop = roundPxValueFromFloat(folderContentPaddingTop * scale); 1650 folderCellLayoutBorderSpacePx = new Point( 1651 roundPxValueFromFloat(folderCellLayoutBorderSpacePx.x * scale), 1652 roundPxValueFromFloat(folderCellLayoutBorderSpacePx.y * scale) 1653 ); 1654 folderFooterHeightPx = roundPxValueFromFloat(folderFooterHeightPx * scale); 1655 folderContentPaddingLeftRight = folderCellLayoutBorderSpacePx.x; 1656 } else { 1657 int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) 1658 * scale); 1659 int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) 1660 * scale); 1661 1662 folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX; 1663 folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight; 1664 folderContentPaddingTop = roundPxValueFromFloat(folderContentPaddingTop * scale); 1665 folderContentPaddingLeftRight = 1666 res.getDimensionPixelSize(R.dimen.folder_content_padding_left_right); 1667 folderFooterHeightPx = 1668 roundPxValueFromFloat( 1669 res.getDimensionPixelSize(R.dimen.folder_footer_height_default) 1670 * scale); 1671 1672 folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight); 1673 } 1674 } 1675 1676 public void updateInsets(Rect insets) { 1677 mInsets.set(insets); 1678 } 1679 1680 /** 1681 * The current device insets. This is generally same as the insets being dispatched to 1682 * {@link Insettable} elements, but can differ if the element is using a different profile. 1683 */ 1684 public Rect getInsets() { 1685 return mInsets; 1686 } 1687 1688 public Point getCellSize() { 1689 return getCellSize(null); 1690 } 1691 1692 public Point getCellSize(Point result) { 1693 if (result == null) { 1694 result = new Point(); 1695 } 1696 1697 int shortcutAndWidgetContainerWidth = 1698 getCellLayoutWidth() - (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right); 1699 result.x = calculateCellWidth(shortcutAndWidgetContainerWidth, cellLayoutBorderSpacePx.x, 1700 inv.numColumns); 1701 int shortcutAndWidgetContainerHeight = 1702 getCellLayoutHeight() - (cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom); 1703 result.y = calculateCellHeight(shortcutAndWidgetContainerHeight, cellLayoutBorderSpacePx.y, 1704 inv.numRows); 1705 return result; 1706 } 1707 1708 /** 1709 * Returns the left and right space on the cell, which is the cell width - icon size 1710 */ 1711 public int getCellHorizontalSpace() { 1712 return getCellSize().x - iconSizePx; 1713 } 1714 1715 /** 1716 * Gets the number of panels within the workspace. 1717 */ 1718 public int getPanelCount() { 1719 return isTwoPanels ? 2 : 1; 1720 } 1721 1722 /** 1723 * Gets the space in px from the bottom of last item in the vertical-bar hotseat to the 1724 * bottom of the screen. 1725 */ 1726 private int getVerticalHotseatLastItemBottomOffset(Context context) { 1727 Rect hotseatBarPadding = getHotseatLayoutPadding(context); 1728 int cellHeight = calculateCellHeight( 1729 heightPx - hotseatBarPadding.top - hotseatBarPadding.bottom, hotseatBorderSpace, 1730 numShownHotseatIcons); 1731 int extraIconEndSpacing = (cellHeight - iconSizePx) / 2; 1732 return extraIconEndSpacing + hotseatBarPadding.bottom; 1733 } 1734 1735 /** 1736 * Gets the scaled top of the workspace in px for the spring-loaded edit state. 1737 */ 1738 public float getCellLayoutSpringLoadShrunkTop() { 1739 return mInsets.top + dropTargetBarTopMarginPx + dropTargetBarSizePx 1740 + dropTargetBarBottomMarginPx; 1741 } 1742 1743 /** 1744 * Gets the scaled bottom of the workspace in px for the spring-loaded edit state. 1745 */ 1746 public float getCellLayoutSpringLoadShrunkBottom(Context context) { 1747 int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx; 1748 return heightPx - (isVerticalBarLayout() 1749 ? getVerticalHotseatLastItemBottomOffset(context) : topOfHotseat); 1750 } 1751 1752 /** 1753 * Gets the scale of the workspace for the spring-loaded edit state. 1754 */ 1755 public float getWorkspaceSpringLoadScale(Context context) { 1756 float scale = 1757 (getCellLayoutSpringLoadShrunkBottom(context) - getCellLayoutSpringLoadShrunkTop()) 1758 / getCellLayoutHeight(); 1759 scale = Math.min(scale, 1f); 1760 1761 // Reduce scale if next pages would not be visible after scaling the workspace. 1762 int workspaceWidth = availableWidthPx; 1763 float scaledWorkspaceWidth = workspaceWidth * scale; 1764 float maxAvailableWidth = workspaceWidth - (2 * workspaceSpringLoadedMinNextPageVisiblePx); 1765 if (scaledWorkspaceWidth > maxAvailableWidth) { 1766 scale *= maxAvailableWidth / scaledWorkspaceWidth; 1767 } 1768 return scale; 1769 } 1770 1771 /** 1772 * Gets the width of a single Cell Layout, aka a single panel within a Workspace. 1773 * 1774 * <p>This is the width of a Workspace, less its horizontal padding. Note that two-panel 1775 * layouts have two Cell Layouts per workspace. 1776 */ 1777 public int getCellLayoutWidth() { 1778 return (availableWidthPx - getTotalWorkspacePadding().x) / getPanelCount(); 1779 } 1780 1781 /** 1782 * Gets the height of a single Cell Layout, aka a single panel within a Workspace. 1783 * 1784 * <p>This is the height of a Workspace, less its vertical padding. 1785 */ 1786 public int getCellLayoutHeight() { 1787 return availableHeightPx - getTotalWorkspacePadding().y; 1788 } 1789 1790 public Point getTotalWorkspacePadding() { 1791 return new Point(workspacePadding.left + workspacePadding.right, 1792 workspacePadding.top + workspacePadding.bottom); 1793 } 1794 1795 /** 1796 * Updates {@link #workspacePadding} as a result of any internal value change to reflect the 1797 * new workspace padding 1798 */ 1799 private void updateWorkspacePadding() { 1800 Rect padding = workspacePadding; 1801 if (isVerticalBarLayout()) { 1802 if (mIsResponsiveGrid) { 1803 padding.top = mResponsiveWorkspaceHeightSpec.getStartPaddingPx(); 1804 padding.bottom = Math.max(0, 1805 mResponsiveWorkspaceHeightSpec.getEndPaddingPx() - mInsets.bottom); 1806 if (isSeascape()) { 1807 padding.left = 1808 hotseatBarSizePx + mResponsiveWorkspaceWidthSpec.getEndPaddingPx(); 1809 padding.right = mResponsiveWorkspaceWidthSpec.getStartPaddingPx(); 1810 } else { 1811 padding.left = mResponsiveWorkspaceWidthSpec.getStartPaddingPx(); 1812 padding.right = 1813 hotseatBarSizePx + mResponsiveWorkspaceWidthSpec.getEndPaddingPx(); 1814 } 1815 } else { 1816 padding.top = 0; 1817 padding.bottom = edgeMarginPx; 1818 if (isSeascape()) { 1819 padding.left = hotseatBarSizePx; 1820 padding.right = mHotseatBarEdgePaddingPx; 1821 } else { 1822 padding.left = mHotseatBarEdgePaddingPx; 1823 padding.right = hotseatBarSizePx; 1824 } 1825 } 1826 } else { 1827 // Pad the bottom of the workspace with hotseat bar 1828 // and leave a bit of space in case a widget go all the way down 1829 int paddingBottom = hotseatBarSizePx + workspaceBottomPadding - mInsets.bottom; 1830 if (!mIsResponsiveGrid) { 1831 paddingBottom += 1832 workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace; 1833 } 1834 int paddingTop = workspaceTopPadding + (mIsScalableGrid ? 0 : edgeMarginPx); 1835 int paddingLeft = desiredWorkspaceHorizontalMarginPx; 1836 int paddingRight = desiredWorkspaceHorizontalMarginPx; 1837 1838 // In fixed Landscape we don't need padding on the side next to the cutout because 1839 // the cutout is already adding padding to all of Launcher, we only need on the other 1840 // side 1841 if (inv.isFixedLandscape) { 1842 paddingLeft = isSeascape() ? desiredWorkspaceHorizontalMarginPx : 0; 1843 paddingRight = isSeascape() ? 0 : desiredWorkspaceHorizontalMarginPx; 1844 } 1845 padding.set(paddingLeft, paddingTop, paddingRight, paddingBottom); 1846 } 1847 insetPadding(workspacePadding, cellLayoutPaddingPx); 1848 } 1849 1850 private void insetPadding(Rect paddings, Rect insets) { 1851 insets.left = Math.min(insets.left, paddings.left); 1852 paddings.left -= insets.left; 1853 1854 insets.top = Math.min(insets.top, paddings.top); 1855 paddings.top -= insets.top; 1856 1857 insets.right = Math.min(insets.right, paddings.right); 1858 paddings.right -= insets.right; 1859 1860 insets.bottom = Math.min(insets.bottom, paddings.bottom); 1861 paddings.bottom -= insets.bottom; 1862 } 1863 1864 1865 /** 1866 * Returns the new border space that should be used between hotseat icons after adjusting it to 1867 * the bubble bar. 1868 * 1869 * <p>Does not check for visible bubbles persistence, so caller should call 1870 * {@link #shouldAdjustHotseatOrQsbForBubbleBar} first. 1871 * 1872 * <p>If there's no adjustment needed, this method returns {@code 0}. 1873 * @see #shouldAdjustHotseatOrQsbForBubbleBar(Context, boolean) 1874 */ 1875 public float getHotseatAdjustedBorderSpaceForBubbleBar(Context context) { 1876 if (shouldAlignBubbleBarWithQSB() || !shouldAdjustHotseatOrQsbForBubbleBar(context)) { 1877 return 0; 1878 } 1879 // The adjustment is shrinking the hotseat's width by 1 icon on either side. 1880 int iconsWidth = 1881 iconSizePx * numShownHotseatIcons + hotseatBorderSpace * (numShownHotseatIcons - 1); 1882 int newWidth = iconsWidth - 2 * iconSizePx; 1883 // Evenly space the icons within the boundaries of the new width. 1884 return (float) (newWidth - iconSizePx * numShownHotseatIcons) / (numShownHotseatIcons - 1); 1885 } 1886 1887 /** 1888 * Returns the hotseat icon translation X for the cellX index. 1889 * 1890 * <p>Does not check for visible bubbles persistence, so caller should call 1891 * {@link #shouldAdjustHotseatOrQsbForBubbleBar} first. 1892 * 1893 * <p>If there's no adjustment needed, this method returns {@code 0}. 1894 * @see #shouldAdjustHotseatOrQsbForBubbleBar(Context, boolean) 1895 */ 1896 public float getHotseatAdjustedTranslation(Context context, int cellX) { 1897 float borderSpace = getHotseatAdjustedBorderSpaceForBubbleBar(context); 1898 if (borderSpace == 0) return borderSpace; 1899 float borderSpaceDelta = borderSpace - hotseatBorderSpace; 1900 return iconSizePx + cellX * borderSpaceDelta; 1901 } 1902 1903 /** Returns whether hotseat or QSB should be adjusted for the bubble bar. */ 1904 public boolean shouldAdjustHotseatOrQsbForBubbleBar(Context context, boolean hasBubbles) { 1905 return hasBubbles && shouldAdjustHotseatOrQsbForBubbleBar(context); 1906 } 1907 1908 /** Returns whether hotseat should be adjusted for the bubble bar. */ 1909 public boolean shouldAdjustHotseatForBubbleBar(Context context, boolean hasBubbles) { 1910 return shouldAlignBubbleBarWithHotseat() 1911 && shouldAdjustHotseatOrQsbForBubbleBar(context, hasBubbles); 1912 } 1913 1914 /** Returns whether hotseat or QSB should be adjusted for the bubble bar. */ 1915 public boolean shouldAdjustHotseatOrQsbForBubbleBar(Context context) { 1916 // only need to adjust if QSB is on top of the hotseat and there's not enough space for the 1917 // bubble bar to either side of the hotseat. 1918 if (isQsbInline) return false; 1919 Rect hotseatPadding = getHotseatLayoutPadding(context); 1920 int hotseatMinHorizontalPadding = Math.min(hotseatPadding.left, hotseatPadding.right); 1921 return hotseatMinHorizontalPadding <= mBubbleBarSpaceThresholdPx; 1922 } 1923 1924 /** 1925 * Returns the padding for hotseat view 1926 */ 1927 public Rect getHotseatLayoutPadding(Context context) { 1928 Rect hotseatBarPadding = new Rect(); 1929 if (isVerticalBarLayout()) { 1930 // The hotseat icons will be placed in the middle of the hotseat cells. 1931 // Changing the hotseatCellHeightPx is not affecting hotseat icon positions 1932 // in vertical bar layout. 1933 int paddingTop = Math.max((int) (mInsets.top + cellLayoutPaddingPx.top), 0); 1934 int paddingBottom = Math.max((int) (mInsets.bottom + cellLayoutPaddingPx.bottom), 0); 1935 1936 if (isSeascape()) { 1937 hotseatBarPadding.set(mInsets.left + mHotseatBarEdgePaddingPx, paddingTop, 1938 mHotseatBarWorkspaceSpacePx, paddingBottom); 1939 } else { 1940 hotseatBarPadding.set(mHotseatBarWorkspaceSpacePx, paddingTop, 1941 mInsets.right + mHotseatBarEdgePaddingPx, paddingBottom); 1942 } 1943 } else if (inv.isFixedLandscape) { 1944 // Center the QSB vertically with hotseat 1945 int hotseatBarBottomPadding = getHotseatBarBottomPadding(); 1946 int hotseatPlusQSBWidth = getHotseatRequiredWidth(); 1947 int qsbWidth = getAdditionalQsbSpace(); 1948 int availableWidthPxForHotseat = availableWidthPx - Math.abs(workspacePadding.width()) 1949 - Math.abs(cellLayoutPaddingPx.width()); 1950 int remainingSpaceOnSide = (availableWidthPxForHotseat - hotseatPlusQSBWidth) / 2; 1951 1952 hotseatBarPadding.set( 1953 remainingSpaceOnSide + mInsets.left + workspacePadding.left 1954 + cellLayoutPaddingPx.left, 1955 hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx, 1956 remainingSpaceOnSide + mInsets.right + workspacePadding.right 1957 + cellLayoutPaddingPx.right, 1958 hotseatBarBottomPadding 1959 ); 1960 if (Utilities.isRtl(context.getResources())) { 1961 hotseatBarPadding.right += qsbWidth; 1962 } else { 1963 hotseatBarPadding.left += qsbWidth; 1964 } 1965 } else if (isTaskbarPresent) { 1966 // Center the QSB vertically with hotseat 1967 int hotseatBarBottomPadding = getHotseatBarBottomPadding(); 1968 int hotseatBarTopPadding = 1969 hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx; 1970 1971 int hotseatWidth = getHotseatRequiredWidth(); 1972 int startSpacing; 1973 int endSpacing; 1974 // Hotseat aligns to the left with nav buttons 1975 if (hotseatBarEndOffset > 0) { 1976 startSpacing = inlineNavButtonsEndSpacingPx; 1977 endSpacing = availableWidthPx - hotseatWidth - startSpacing + hotseatBorderSpace; 1978 } else { 1979 startSpacing = (availableWidthPx - hotseatWidth) / 2; 1980 endSpacing = startSpacing; 1981 } 1982 startSpacing += getAdditionalQsbSpace(); 1983 1984 hotseatBarPadding.top = hotseatBarTopPadding; 1985 hotseatBarPadding.bottom = hotseatBarBottomPadding; 1986 boolean isRtl = Utilities.isRtl(context.getResources()); 1987 if (isRtl) { 1988 hotseatBarPadding.left = endSpacing; 1989 hotseatBarPadding.right = startSpacing; 1990 } else { 1991 hotseatBarPadding.left = startSpacing; 1992 hotseatBarPadding.right = endSpacing; 1993 } 1994 1995 } else if (mIsScalableGrid) { 1996 int iconExtraSpacePx = iconSizePx - getIconVisibleSizePx(iconSizePx); 1997 int sideSpacing = (availableWidthPx - (hotseatQsbWidth + iconExtraSpacePx)) / 2; 1998 hotseatBarPadding.set(sideSpacing, 1999 0, 2000 sideSpacing, 2001 getHotseatBarBottomPadding()); 2002 } else { 2003 // We want the edges of the hotseat to line up with the edges of the workspace, but the 2004 // icons in the hotseat are a different size, and so don't line up perfectly. To account 2005 // for this, we pad the left and right of the hotseat with half of the difference of a 2006 // workspace cell vs a hotseat cell. 2007 float workspaceCellWidth = (float) widthPx / inv.numColumns; 2008 float hotseatCellWidth = (float) widthPx / numShownHotseatIcons; 2009 int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2); 2010 hotseatBarPadding.set( 2011 hotseatAdjustment + workspacePadding.left + cellLayoutPaddingPx.left 2012 + mInsets.left, 2013 0, 2014 hotseatAdjustment + workspacePadding.right + cellLayoutPaddingPx.right 2015 + mInsets.right, 2016 getHotseatBarBottomPadding()); 2017 } 2018 return hotseatBarPadding; 2019 } 2020 2021 /** The margin between the edge of all apps and the edge of the first icon. */ 2022 public int getAllAppsIconStartMargin(Context context) { 2023 int allAppsSpacing; 2024 if (isVerticalBarLayout()) { 2025 // On phones, the landscape layout uses a different setup. 2026 allAppsSpacing = workspacePadding.left + workspacePadding.right; 2027 } else { 2028 allAppsSpacing = 2029 allAppsPadding.left + allAppsPadding.right + allAppsLeftRightMargin * 2; 2030 } 2031 2032 int cellWidth = DeviceProfile.calculateCellWidth( 2033 availableWidthPx - allAppsSpacing, 2034 0 /* borderSpace */, 2035 numShownAllAppsColumns); 2036 int iconAlignmentMargin = (cellWidth - getIconVisibleSizePx(allAppsIconSizePx)) / 2; 2037 2038 return (Utilities.isRtl(context.getResources()) ? allAppsPadding.right 2039 : allAppsPadding.left) + iconAlignmentMargin; 2040 } 2041 2042 /** 2043 * TODO(b/235886078): workaround needed because of this bug 2044 * Icons are 10% larger on XML than their visual size, so remove that extra space to get 2045 * some dimensions correct. 2046 * 2047 * When this bug is resolved this method will no longer be needed and we would be able to 2048 * replace all instances where this method is called with iconSizePx. 2049 */ 2050 private int getIconVisibleSizePx(int iconSizePx) { 2051 return Math.round(ICON_VISIBLE_AREA_FACTOR * iconSizePx); 2052 } 2053 2054 private int getAdditionalQsbSpace() { 2055 return isQsbInline ? hotseatQsbWidth + hotseatBorderSpace : 0; 2056 } 2057 2058 /** 2059 * Calculate how much space the hotseat needs to be shown completely 2060 */ 2061 private int getHotseatRequiredWidth() { 2062 int additionalQsbSpace = getAdditionalQsbSpace(); 2063 return iconSizePx * numShownHotseatIcons 2064 + hotseatBorderSpace * (numShownHotseatIcons - (areNavButtonsInline ? 0 : 1)) 2065 + additionalQsbSpace; 2066 } 2067 2068 /** 2069 * Returns the number of pixels the QSB is translated from the bottom of the screen. 2070 */ 2071 public int getQsbOffsetY() { 2072 if (isQsbInline) { 2073 return getHotseatBarBottomPadding() - ((hotseatQsbHeight - hotseatCellHeightPx) / 2); 2074 } else if (isTaskbarPresent) { // QSB on top 2075 return hotseatBarSizePx - hotseatQsbHeight + hotseatQsbShadowHeight; 2076 } else { 2077 return hotseatBarBottomSpacePx - hotseatQsbShadowHeight; 2078 } 2079 } 2080 2081 /** 2082 * Returns the number of pixels the hotseat is translated from the bottom of the screen. 2083 */ 2084 private int getHotseatBarBottomPadding() { 2085 if (isTaskbarPresent || isQsbInline) { // QSB on top or inline 2086 return hotseatBarBottomSpacePx - (Math.abs(hotseatCellHeightPx - iconSizePx) / 2); 2087 } else { 2088 return hotseatBarSizePx - hotseatCellHeightPx; 2089 } 2090 } 2091 2092 /** 2093 * Returns the number of pixels the hotseat icons or QSB vertical center is translated from the 2094 * bottom of the screen. 2095 */ 2096 public int getBubbleBarVerticalCenterForHome() { 2097 if (shouldAlignBubbleBarWithHotseat()) { 2098 return hotseatBarSizePx 2099 - (isQsbInline ? 0 : hotseatQsbVisualHeight) 2100 - hotseatQsbSpace 2101 - (hotseatCellHeightPx / 2) 2102 + ((hotseatCellHeightPx - iconSizePx) / 2); 2103 } else { 2104 return hotseatBarSizePx - (hotseatQsbVisualHeight / 2); 2105 } 2106 } 2107 2108 /** Returns whether bubble bar should be aligned with the hotseat. */ 2109 public boolean shouldAlignBubbleBarWithQSB() { 2110 return !shouldAlignBubbleBarWithHotseat(); 2111 } 2112 2113 /** Returns whether bubble bar should be aligned with the hotseat. */ 2114 public boolean shouldAlignBubbleBarWithHotseat() { 2115 return isQsbInline || isGestureMode; 2116 } 2117 2118 /** 2119 * Returns the number of pixels the taskbar is translated from the bottom of the screen. 2120 */ 2121 public int getTaskbarOffsetY() { 2122 int taskbarIconBottomSpace = (taskbarHeight - iconSizePx) / 2; 2123 int launcherIconBottomSpace = 2124 Math.min((hotseatCellHeightPx - iconSizePx) / 2, gridVisualizationPaddingY); 2125 return getHotseatBarBottomPadding() + launcherIconBottomSpace - taskbarIconBottomSpace; 2126 } 2127 2128 /** Returns the number of pixels required below OverviewActions. */ 2129 public int getOverviewActionsClaimedSpaceBelow() { 2130 return isTaskbarPresent ? mTransientTaskbarClaimedSpace : mInsets.bottom; 2131 } 2132 2133 /** Gets the space that the overview actions will take, including bottom margin. */ 2134 public int getOverviewActionsClaimedSpace() { 2135 int overviewActionsSpace = isTablet && Flags.enableGridOnlyOverview() 2136 ? 0 2137 : (overviewActionsTopMarginPx + overviewActionsHeight); 2138 return overviewActionsSpace + getOverviewActionsClaimedSpaceBelow(); 2139 } 2140 2141 /** 2142 * Takes the View and return the scales of width and height depending on the DeviceProfile 2143 * specifications 2144 * 2145 * @param itemInfo The tag of the widget view 2146 * @return A PointF instance with the x set to be the scale of width, and y being the scale of 2147 * height 2148 */ 2149 @NonNull 2150 public PointF getAppWidgetScale(@Nullable final ItemInfo itemInfo) { 2151 return mViewScaleProvider.getScaleFromItemInfo(itemInfo); 2152 } 2153 2154 /** 2155 * @return the bounds for which the open folders should be contained within 2156 */ 2157 public Rect getAbsoluteOpenFolderBounds() { 2158 if (isVerticalBarLayout()) { 2159 // Folders should only appear right of the drop target bar and left of the hotseat 2160 return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx, 2161 mInsets.top, 2162 mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx, 2163 mInsets.top + availableHeightPx); 2164 } else { 2165 // Folders should only appear below the drop target bar and above the hotseat 2166 int hotseatTop = isTaskbarPresent ? taskbarHeight : hotseatBarSizePx; 2167 return new Rect(mInsets.left + edgeMarginPx, 2168 mInsets.top + dropTargetBarSizePx + edgeMarginPx, 2169 mInsets.left + availableWidthPx - edgeMarginPx, 2170 mInsets.top + availableHeightPx - hotseatTop 2171 - workspacePageIndicatorHeight - edgeMarginPx); 2172 } 2173 } 2174 2175 public static int calculateCellWidth(int width, int borderSpacing, int countX) { 2176 return (width - ((countX - 1) * borderSpacing)) / countX; 2177 } 2178 2179 public static int calculateCellHeight(int height, int borderSpacing, int countY) { 2180 return (height - ((countY - 1) * borderSpacing)) / countY; 2181 } 2182 2183 /** 2184 * When {@code true}, the device is in landscape mode and the hotseat is on the right column. 2185 * When {@code false}, either device is in portrait mode or the device is in landscape mode and 2186 * the hotseat is on the bottom row. 2187 */ 2188 public boolean isVerticalBarLayout() { 2189 return isLandscape && transposeLayoutWithOrientation; 2190 } 2191 2192 public boolean isSeascape() { 2193 return rotationHint == Surface.ROTATION_270 2194 && (isVerticalBarLayout() || inv.isFixedLandscape); 2195 } 2196 2197 public boolean shouldFadeAdjacentWorkspaceScreens() { 2198 return isVerticalBarLayout(); 2199 } 2200 2201 public int getCellContentHeight(@ContainerType int containerType) { 2202 switch (containerType) { 2203 case CellLayout.WORKSPACE: 2204 return cellHeightPx; 2205 case CellLayout.FOLDER: 2206 return folderCellHeightPx; 2207 case CellLayout.HOTSEAT: 2208 // The hotseat is the only container where the cell height is going to be 2209 // different from the content within that cell. 2210 return iconSizePx; 2211 default: 2212 // ?? 2213 return 0; 2214 } 2215 } 2216 2217 private String pxToDpStr(String name, float value) { 2218 return "\t" + name + ": " + value + "px (" + dpiFromPx(value, mMetrics.densityDpi) + "dp)"; 2219 } 2220 2221 private String dpPointFToString(String name, PointF value) { 2222 return String.format(Locale.ENGLISH, "\t%s: PointF(%.1f, %.1f)dp", name, value.x, value.y); 2223 } 2224 2225 /** Dumps various DeviceProfile variables to the specified writer. */ 2226 public void dump(Context context, String prefix, PrintWriter writer) { 2227 writer.println(prefix + "DeviceProfile:"); 2228 writer.println(prefix + "\t1 dp = " + mMetrics.density + " px"); 2229 2230 writer.println(prefix + "\tisTablet:" + isTablet); 2231 writer.println(prefix + "\tisPhone:" + isPhone); 2232 writer.println(prefix + "\ttransposeLayoutWithOrientation:" 2233 + transposeLayoutWithOrientation); 2234 writer.println(prefix + "\tisGestureMode:" + isGestureMode); 2235 2236 writer.println(prefix + "\tisLandscape:" + isLandscape); 2237 writer.println(prefix + "\tisMultiWindowMode:" + isMultiWindowMode); 2238 writer.println(prefix + "\tisTwoPanels:" + isTwoPanels); 2239 writer.println(prefix + "\tisLeftRightSplit:" + isLeftRightSplit); 2240 2241 writer.println(prefix + pxToDpStr("windowX", windowX)); 2242 writer.println(prefix + pxToDpStr("windowY", windowY)); 2243 writer.println(prefix + pxToDpStr("widthPx", widthPx)); 2244 writer.println(prefix + pxToDpStr("heightPx", heightPx)); 2245 writer.println(prefix + pxToDpStr("availableWidthPx", availableWidthPx)); 2246 writer.println(prefix + pxToDpStr("availableHeightPx", availableHeightPx)); 2247 writer.println(prefix + pxToDpStr("mInsets.left", mInsets.left)); 2248 writer.println(prefix + pxToDpStr("mInsets.top", mInsets.top)); 2249 writer.println(prefix + pxToDpStr("mInsets.right", mInsets.right)); 2250 writer.println(prefix + pxToDpStr("mInsets.bottom", mInsets.bottom)); 2251 2252 writer.println(prefix + "\taspectRatio:" + aspectRatio); 2253 2254 writer.println(prefix + "\tisResponsiveGrid:" + mIsResponsiveGrid); 2255 writer.println(prefix + "\tisScalableGrid:" + mIsScalableGrid); 2256 2257 writer.println(prefix + "\tinv.numRows: " + inv.numRows); 2258 writer.println(prefix + "\tinv.numColumns: " + inv.numColumns); 2259 writer.println(prefix + "\tinv.numSearchContainerColumns: " 2260 + inv.numSearchContainerColumns); 2261 2262 writer.println(prefix + dpPointFToString("minCellSize", inv.minCellSize[mTypeIndex])); 2263 2264 writer.println(prefix + pxToDpStr("cellWidthPx", cellWidthPx)); 2265 writer.println(prefix + pxToDpStr("cellHeightPx", cellHeightPx)); 2266 2267 writer.println(prefix + pxToDpStr("getCellSize().x", getCellSize().x)); 2268 writer.println(prefix + pxToDpStr("getCellSize().y", getCellSize().y)); 2269 2270 writer.println(prefix + pxToDpStr("cellLayoutBorderSpacePx Horizontal", 2271 cellLayoutBorderSpacePx.x)); 2272 writer.println(prefix + pxToDpStr("cellLayoutBorderSpacePx Vertical", 2273 cellLayoutBorderSpacePx.y)); 2274 writer.println( 2275 prefix + pxToDpStr("cellLayoutPaddingPx.left", cellLayoutPaddingPx.left)); 2276 writer.println( 2277 prefix + pxToDpStr("cellLayoutPaddingPx.top", cellLayoutPaddingPx.top)); 2278 writer.println( 2279 prefix + pxToDpStr("cellLayoutPaddingPx.right", cellLayoutPaddingPx.right)); 2280 writer.println( 2281 prefix + pxToDpStr("cellLayoutPaddingPx.bottom", cellLayoutPaddingPx.bottom)); 2282 2283 writer.println(prefix + pxToDpStr("iconSizePx", iconSizePx)); 2284 writer.println(prefix + pxToDpStr("iconTextSizePx", iconTextSizePx)); 2285 writer.println(prefix + pxToDpStr("iconDrawablePaddingPx", iconDrawablePaddingPx)); 2286 2287 writer.println(prefix + "\tnumFolderRows: " + numFolderRows); 2288 writer.println(prefix + "\tnumFolderColumns: " + numFolderColumns); 2289 writer.println(prefix + pxToDpStr("folderCellWidthPx", folderCellWidthPx)); 2290 writer.println(prefix + pxToDpStr("folderCellHeightPx", folderCellHeightPx)); 2291 writer.println(prefix + pxToDpStr("folderChildIconSizePx", folderChildIconSizePx)); 2292 writer.println(prefix + pxToDpStr("folderChildTextSizePx", folderChildTextSizePx)); 2293 writer.println(prefix + pxToDpStr("folderChildDrawablePaddingPx", 2294 folderChildDrawablePaddingPx)); 2295 writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx.x", 2296 folderCellLayoutBorderSpacePx.x)); 2297 writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx.y", 2298 folderCellLayoutBorderSpacePx.y)); 2299 writer.println(prefix + pxToDpStr("folderContentPaddingLeftRight", 2300 folderContentPaddingLeftRight)); 2301 writer.println(prefix + pxToDpStr("folderTopPadding", folderContentPaddingTop)); 2302 writer.println(prefix + pxToDpStr("folderFooterHeight", folderFooterHeightPx)); 2303 2304 writer.println(prefix + pxToDpStr("bottomSheetTopPadding", bottomSheetTopPadding)); 2305 writer.println(prefix + "\tbottomSheetOpenDuration: " + bottomSheetOpenDuration); 2306 writer.println(prefix + "\tbottomSheetCloseDuration: " + bottomSheetCloseDuration); 2307 writer.println(prefix + "\tbottomSheetWorkspaceScale: " + bottomSheetWorkspaceScale); 2308 writer.println(prefix + "\tbottomSheetDepth: " + bottomSheetDepth); 2309 2310 writer.println(prefix + pxToDpStr("allAppsShiftRange", allAppsShiftRange)); 2311 writer.println(prefix + "\tallAppsOpenDuration: " + allAppsOpenDuration); 2312 writer.println(prefix + "\tallAppsCloseDuration: " + allAppsCloseDuration); 2313 writer.println(prefix + pxToDpStr("allAppsIconSizePx", allAppsIconSizePx)); 2314 writer.println(prefix + pxToDpStr("allAppsIconTextSizePx", allAppsIconTextSizePx)); 2315 writer.println(prefix + pxToDpStr("allAppsIconDrawablePaddingPx", 2316 allAppsIconDrawablePaddingPx)); 2317 writer.println(prefix + pxToDpStr("allAppsCellHeightPx", allAppsCellHeightPx)); 2318 writer.println(prefix + pxToDpStr("allAppsCellWidthPx", allAppsCellWidthPx)); 2319 writer.println(prefix + pxToDpStr("allAppsBorderSpacePxX", allAppsBorderSpacePx.x)); 2320 writer.println(prefix + pxToDpStr("allAppsBorderSpacePxY", allAppsBorderSpacePx.y)); 2321 writer.println(prefix + "\tnumShownAllAppsColumns: " + numShownAllAppsColumns); 2322 writer.println(prefix + pxToDpStr("allAppsPadding.top", allAppsPadding.top)); 2323 writer.println(prefix + pxToDpStr("allAppsPadding.left", allAppsPadding.left)); 2324 writer.println(prefix + pxToDpStr("allAppsPadding.right", allAppsPadding.right)); 2325 writer.println(prefix + pxToDpStr("allAppsLeftRightMargin", allAppsLeftRightMargin)); 2326 2327 writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx)); 2328 writer.println(prefix + "\tmHotseatColumnSpan: " + mHotseatColumnSpan); 2329 writer.println(prefix + pxToDpStr("mHotseatWidthPx", mHotseatWidthPx)); 2330 writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx)); 2331 writer.println(prefix + pxToDpStr("hotseatBarBottomSpacePx", hotseatBarBottomSpacePx)); 2332 writer.println(prefix + pxToDpStr("mHotseatBarEdgePaddingPx", 2333 mHotseatBarEdgePaddingPx)); 2334 writer.println(prefix + pxToDpStr("mHotseatBarWorkspaceSpacePx", 2335 mHotseatBarWorkspaceSpacePx)); 2336 writer.println(prefix 2337 + pxToDpStr("inlineNavButtonsEndSpacingPx", inlineNavButtonsEndSpacingPx)); 2338 writer.println(prefix 2339 + pxToDpStr("navButtonsLayoutWidthPx", navButtonsLayoutWidthPx)); 2340 writer.println(prefix + pxToDpStr("hotseatBarEndOffset", hotseatBarEndOffset)); 2341 writer.println(prefix + pxToDpStr("hotseatQsbSpace", hotseatQsbSpace)); 2342 writer.println(prefix + pxToDpStr("hotseatQsbHeight", hotseatQsbHeight)); 2343 writer.println(prefix + pxToDpStr("springLoadedHotseatBarTopMarginPx", 2344 springLoadedHotseatBarTopMarginPx)); 2345 Rect hotseatLayoutPadding = getHotseatLayoutPadding(context); 2346 writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).top", 2347 hotseatLayoutPadding.top)); 2348 writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).bottom", 2349 hotseatLayoutPadding.bottom)); 2350 writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).left", 2351 hotseatLayoutPadding.left)); 2352 writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).right", 2353 hotseatLayoutPadding.right)); 2354 writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons); 2355 writer.println(prefix + pxToDpStr("hotseatBorderSpace", hotseatBorderSpace)); 2356 writer.println(prefix + "\tisQsbInline: " + isQsbInline); 2357 writer.println(prefix + pxToDpStr("hotseatQsbWidth", hotseatQsbWidth)); 2358 2359 writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent); 2360 writer.println(prefix + "\tisTaskbarPresentInApps:" + isTaskbarPresentInApps); 2361 writer.println(prefix + pxToDpStr("taskbarHeight", taskbarHeight)); 2362 writer.println(prefix + pxToDpStr("stashedTaskbarHeight", stashedTaskbarHeight)); 2363 writer.println(prefix + pxToDpStr("taskbarBottomMargin", taskbarBottomMargin)); 2364 writer.println(prefix + pxToDpStr("taskbarIconSize", taskbarIconSize)); 2365 2366 writer.println(prefix + pxToDpStr("desiredWorkspaceHorizontalMarginPx", 2367 desiredWorkspaceHorizontalMarginPx)); 2368 writer.println(prefix + pxToDpStr("workspacePadding.left", workspacePadding.left)); 2369 writer.println(prefix + pxToDpStr("workspacePadding.top", workspacePadding.top)); 2370 writer.println(prefix + pxToDpStr("workspacePadding.right", workspacePadding.right)); 2371 writer.println(prefix + pxToDpStr("workspacePadding.bottom", workspacePadding.bottom)); 2372 2373 writer.println(prefix + pxToDpStr("iconScale", iconScale)); 2374 writer.println(prefix + pxToDpStr("cellScaleToFit ", cellScaleToFit)); 2375 writer.println(prefix + pxToDpStr("extraSpace", extraSpace)); 2376 writer.println(prefix + pxToDpStr("unscaled extraSpace", extraSpace / iconScale)); 2377 2378 writer.println(prefix + pxToDpStr("maxEmptySpace", maxEmptySpace)); 2379 writer.println(prefix + pxToDpStr("workspaceTopPadding", workspaceTopPadding)); 2380 writer.println(prefix + pxToDpStr("workspaceBottomPadding", workspaceBottomPadding)); 2381 2382 writer.println(prefix + pxToDpStr("overviewTaskMarginPx", overviewTaskMarginPx)); 2383 writer.println(prefix + pxToDpStr("overviewTaskIconSizePx", overviewTaskIconSizePx)); 2384 writer.println(prefix + pxToDpStr("overviewTaskIconDrawableSizePx", 2385 overviewTaskIconDrawableSizePx)); 2386 writer.println(prefix + pxToDpStr("overviewTaskIconDrawableSizeGridPx", 2387 overviewTaskIconDrawableSizeGridPx)); 2388 writer.println(prefix + pxToDpStr("overviewTaskThumbnailTopMarginPx", 2389 overviewTaskThumbnailTopMarginPx)); 2390 writer.println(prefix + pxToDpStr("overviewActionsTopMarginPx", 2391 overviewActionsTopMarginPx)); 2392 writer.println(prefix + pxToDpStr("overviewActionsHeight", 2393 overviewActionsHeight)); 2394 writer.println(prefix + pxToDpStr("overviewActionsClaimedSpaceBelow", 2395 getOverviewActionsClaimedSpaceBelow())); 2396 writer.println(prefix + pxToDpStr("overviewActionsButtonSpacing", 2397 overviewActionsButtonSpacing)); 2398 writer.println(prefix + pxToDpStr("overviewPageSpacing", overviewPageSpacing)); 2399 writer.println(prefix + pxToDpStr("overviewRowSpacing", overviewRowSpacing)); 2400 writer.println(prefix + pxToDpStr("overviewGridSideMargin", overviewGridSideMargin)); 2401 2402 writer.println(prefix + pxToDpStr("dropTargetBarTopMarginPx", dropTargetBarTopMarginPx)); 2403 writer.println(prefix + pxToDpStr("dropTargetBarSizePx", dropTargetBarSizePx)); 2404 writer.println( 2405 prefix + pxToDpStr("dropTargetBarBottomMarginPx", dropTargetBarBottomMarginPx)); 2406 2407 writer.println(prefix + pxToDpStr("getCellLayoutSpringLoadShrunkTop()", 2408 getCellLayoutSpringLoadShrunkTop())); 2409 writer.println(prefix + pxToDpStr("getCellLayoutSpringLoadShrunkBottom()", 2410 getCellLayoutSpringLoadShrunkBottom(context))); 2411 writer.println(prefix + pxToDpStr("workspaceSpringLoadedMinNextPageVisiblePx", 2412 workspaceSpringLoadedMinNextPageVisiblePx)); 2413 writer.println(prefix + pxToDpStr("getWorkspaceSpringLoadScale()", 2414 getWorkspaceSpringLoadScale(context))); 2415 writer.println(prefix + pxToDpStr("getCellLayoutHeight()", getCellLayoutHeight())); 2416 writer.println(prefix + pxToDpStr("getCellLayoutWidth()", getCellLayoutWidth())); 2417 if (mIsResponsiveGrid) { 2418 writer.println(prefix + "\tmResponsiveWorkspaceHeightSpec:" 2419 + mResponsiveWorkspaceHeightSpec.toString()); 2420 writer.println(prefix + "\tmResponsiveWorkspaceWidthSpec:" 2421 + mResponsiveWorkspaceWidthSpec.toString()); 2422 writer.println(prefix + "\tmResponsiveAllAppsHeightSpec:" 2423 + mResponsiveAllAppsHeightSpec.toString()); 2424 writer.println(prefix + "\tmResponsiveAllAppsWidthSpec:" 2425 + mResponsiveAllAppsWidthSpec.toString()); 2426 writer.println(prefix + "\tmResponsiveFolderHeightSpec:" + mResponsiveFolderHeightSpec); 2427 writer.println(prefix + "\tmResponsiveFolderWidthSpec:" + mResponsiveFolderWidthSpec); 2428 writer.println(prefix + "\tmResponsiveHotseatSpec:" + mResponsiveHotseatSpec); 2429 writer.println(prefix + "\tmResponsiveWorkspaceCellSpec:" 2430 + mResponsiveWorkspaceCellSpec); 2431 writer.println(prefix + "\tmResponsiveAllAppsCellSpec:" + mResponsiveAllAppsCellSpec); 2432 } 2433 } 2434 2435 /** Returns a reduced representation of this DeviceProfile. */ 2436 public String toSmallString() { 2437 return "isTablet:" + isTablet + ", " 2438 + "isMultiDisplay:" + isMultiDisplay + ", " 2439 + "widthPx:" + widthPx + ", " 2440 + "heightPx:" + heightPx + ", " 2441 + "insets:" + mInsets + ", " 2442 + "rotationHint:" + rotationHint; 2443 } 2444 2445 private static Context getContext(Context c, Info info, int orientation, WindowBounds bounds) { 2446 Configuration config = new Configuration(c.getResources().getConfiguration()); 2447 config.orientation = orientation; 2448 config.densityDpi = info.getDensityDpi(); 2449 config.smallestScreenWidthDp = (int) info.smallestSizeDp(bounds); 2450 return c.createConfigurationContext(config); 2451 } 2452 2453 /** 2454 * Returns whether Taskbar and Hotseat should adjust horizontally on bubble bar location update. 2455 */ 2456 public boolean shouldAdjustHotseatOnNavBarLocationUpdate(Context context) { 2457 return enableBubbleBar() 2458 && !DisplayController.getNavigationMode(context).hasGestures; 2459 } 2460 2461 /** Returns hotseat translation X for the bubble bar position. */ 2462 public int getHotseatTranslationXForNavBar(Context context, boolean isBubblesOnLeft) { 2463 if (shouldAdjustHotseatOnNavBarLocationUpdate(context)) { 2464 boolean isRtl = Utilities.isRtl(context.getResources()); 2465 if (isBubblesOnLeft) { 2466 return isRtl ? -navButtonsLayoutWidthPx : 0; 2467 } else { 2468 return isRtl ? 0 : navButtonsLayoutWidthPx; 2469 } 2470 } else { 2471 return 0; 2472 } 2473 } 2474 2475 /** 2476 * Callback when a component changes the DeviceProfile associated with it, as a result of 2477 * configuration change 2478 */ 2479 public interface OnDeviceProfileChangeListener { 2480 2481 /** 2482 * Called when the device profile is reassigned. Note that for layout and measurements, it 2483 * is sufficient to listen for inset changes. Use this callback when you need to perform 2484 * a one time operation. 2485 */ 2486 void onDeviceProfileChanged(DeviceProfile dp); 2487 } 2488 2489 /** 2490 * Handler that deals with ItemInfo of the views for the DeviceProfile 2491 */ 2492 @FunctionalInterface 2493 public interface ViewScaleProvider { 2494 /** 2495 * Get the scales from the view 2496 * 2497 * @param itemInfo The tag of the widget view 2498 * @return PointF instance containing the scale information, or null if using the default 2499 * app widget scale of this device profile. 2500 */ 2501 @NonNull 2502 PointF getScaleFromItemInfo(@Nullable ItemInfo itemInfo); 2503 } 2504 2505 public static class Builder { 2506 private final Context mContext; 2507 private final InvariantDeviceProfile mInv; 2508 private final Info mInfo; 2509 private final WindowManagerProxy mWMProxy; 2510 private final ThemeManager mThemeManager; 2511 2512 private WindowBounds mWindowBounds; 2513 private boolean mIsMultiDisplay; 2514 2515 private boolean mIsMultiWindowMode = false; 2516 private Boolean mTransposeLayoutWithOrientation; 2517 private Boolean mIsGestureMode; 2518 private ViewScaleProvider mViewScaleProvider = null; 2519 2520 private SparseArray<DotRenderer> mDotRendererCache; 2521 2522 private Consumer<DeviceProfile> mOverrideProvider; 2523 2524 private boolean mIsTransientTaskbar; 2525 2526 public Builder(Context context, InvariantDeviceProfile inv, Info info, 2527 WindowManagerProxy wmProxy, ThemeManager themeManager) { 2528 mContext = context; 2529 mInv = inv; 2530 mInfo = info; 2531 mWMProxy = wmProxy; 2532 mThemeManager = themeManager; 2533 mIsTransientTaskbar = info.isTransientTaskbar(); 2534 } 2535 2536 public Builder setMultiWindowMode(boolean isMultiWindowMode) { 2537 mIsMultiWindowMode = isMultiWindowMode; 2538 return this; 2539 } 2540 2541 public Builder setIsMultiDisplay(boolean isMultiDisplay) { 2542 mIsMultiDisplay = isMultiDisplay; 2543 return this; 2544 } 2545 2546 public Builder setDotRendererCache(SparseArray<DotRenderer> dotRendererCache) { 2547 mDotRendererCache = dotRendererCache; 2548 return this; 2549 } 2550 2551 public Builder setWindowBounds(WindowBounds bounds) { 2552 mWindowBounds = bounds; 2553 return this; 2554 } 2555 2556 public Builder setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation) { 2557 mTransposeLayoutWithOrientation = transposeLayoutWithOrientation; 2558 return this; 2559 } 2560 2561 public Builder setGestureMode(boolean isGestureMode) { 2562 mIsGestureMode = isGestureMode; 2563 return this; 2564 } 2565 2566 public Builder withDimensionsOverride(Consumer<DeviceProfile> overrideProvider) { 2567 mOverrideProvider = overrideProvider; 2568 return this; 2569 } 2570 2571 /** 2572 * Set the viewScaleProvider for the builder 2573 * 2574 * @param viewScaleProvider The viewScaleProvider to be set for the 2575 * DeviceProfile 2576 * @return This builder 2577 */ 2578 @NonNull 2579 public Builder setViewScaleProvider(@Nullable ViewScaleProvider viewScaleProvider) { 2580 mViewScaleProvider = viewScaleProvider; 2581 return this; 2582 } 2583 2584 /** 2585 * Set the isTransientTaskbar for the builder 2586 * @return This Builder 2587 */ 2588 public Builder setIsTransientTaskbar(boolean isTransientTaskbar) { 2589 mIsTransientTaskbar = isTransientTaskbar; 2590 return this; 2591 } 2592 2593 public DeviceProfile build() { 2594 if (mWindowBounds == null) { 2595 throw new IllegalArgumentException("Window bounds not set"); 2596 } 2597 if (mTransposeLayoutWithOrientation == null) { 2598 mTransposeLayoutWithOrientation = 2599 !(mInfo.isTablet(mWindowBounds) || mInv.isFixedLandscape); 2600 } 2601 if (mIsGestureMode == null) { 2602 mIsGestureMode = mInfo.getNavigationMode().hasGestures; 2603 } 2604 if (mDotRendererCache == null) { 2605 mDotRendererCache = new SparseArray<>(); 2606 } 2607 if (mViewScaleProvider == null) { 2608 mViewScaleProvider = DEFAULT_PROVIDER; 2609 } 2610 if (mOverrideProvider == null) { 2611 mOverrideProvider = DEFAULT_DIMENSION_PROVIDER; 2612 } 2613 return new DeviceProfile(mContext, mInv, mInfo, mWMProxy, mThemeManager, 2614 mWindowBounds, mDotRendererCache, 2615 mIsMultiWindowMode, mTransposeLayoutWithOrientation, mIsMultiDisplay, 2616 mIsGestureMode, mViewScaleProvider, mOverrideProvider, mIsTransientTaskbar); 2617 } 2618 } 2619 } 2620