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