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