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