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 android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; 20 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; 21 22 import static com.android.launcher3.ResourceUtils.pxFromDp; 23 import static com.android.launcher3.Utilities.dpiFromPx; 24 import static com.android.launcher3.Utilities.pxFromSp; 25 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR; 26 import static com.android.launcher3.util.WindowManagerCompat.MIN_TABLET_WIDTH; 27 28 import android.annotation.SuppressLint; 29 import android.content.Context; 30 import android.content.res.Configuration; 31 import android.content.res.Resources; 32 import android.graphics.Path; 33 import android.graphics.Point; 34 import android.graphics.PointF; 35 import android.graphics.Rect; 36 import android.hardware.display.DisplayManager; 37 import android.util.DisplayMetrics; 38 import android.view.Surface; 39 import android.view.WindowInsets; 40 import android.view.WindowManager; 41 42 import com.android.launcher3.CellLayout.ContainerType; 43 import com.android.launcher3.DevicePaddings.DevicePadding; 44 import com.android.launcher3.config.FeatureFlags; 45 import com.android.launcher3.icons.DotRenderer; 46 import com.android.launcher3.icons.GraphicsUtils; 47 import com.android.launcher3.icons.IconNormalizer; 48 import com.android.launcher3.util.DisplayController; 49 import com.android.launcher3.util.DisplayController.Info; 50 import com.android.launcher3.util.WindowBounds; 51 52 import java.io.PrintWriter; 53 54 @SuppressLint("NewApi") 55 public class DeviceProfile { 56 57 private static final int DEFAULT_DOT_SIZE = 100; 58 59 public final InvariantDeviceProfile inv; 60 private final Info mInfo; 61 private final DisplayMetrics mMetrics; 62 63 // Device properties 64 public final boolean isTablet; 65 public final boolean isPhone; 66 public final boolean transposeLayoutWithOrientation; 67 public final boolean isTwoPanels; 68 public final boolean allowRotation; 69 70 // Device properties in current orientation 71 public final boolean isLandscape; 72 public final boolean isMultiWindowMode; 73 74 public final int windowX; 75 public final int windowY; 76 public final int widthPx; 77 public final int heightPx; 78 public final int availableWidthPx; 79 public final int availableHeightPx; 80 81 public final float aspectRatio; 82 83 public final boolean isScalableGrid; 84 85 /** 86 * The maximum amount of left/right workspace padding as a percentage of the screen width. 87 * To be clear, this means that up to 7% of the screen width can be used as left padding, and 88 * 7% of the screen width can be used as right padding. 89 */ 90 private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f; 91 92 private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f; 93 private static final float TALLER_DEVICE_ASPECT_RATIO_THRESHOLD = 2.15f; 94 private static final float TALL_DEVICE_EXTRA_SPACE_THRESHOLD_DP = 252; 95 private static final float TALL_DEVICE_MORE_EXTRA_SPACE_THRESHOLD_DP = 268; 96 97 // To evenly space the icons, increase the left/right margins for tablets in portrait mode. 98 private static final int PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER = 4; 99 100 // Workspace 101 public final int desiredWorkspaceLeftRightOriginalPx; 102 public int desiredWorkspaceLeftRightMarginPx; 103 public final int cellLayoutBorderSpacingOriginalPx; 104 public int cellLayoutBorderSpacingPx; 105 public final int cellLayoutPaddingLeftRightPx; 106 public final int cellLayoutBottomPaddingPx; 107 public final int edgeMarginPx; 108 public float workspaceSpringLoadShrinkFactor; 109 public final int workspaceSpringLoadedBottomSpace; 110 111 private final int extraSpace; 112 public int workspaceTopPadding; 113 public int workspaceBottomPadding; 114 public int extraHotseatBottomPadding; 115 116 // Workspace page indicator 117 public final int workspacePageIndicatorHeight; 118 private final int mWorkspacePageIndicatorOverlapWorkspace; 119 120 // Workspace icons 121 public float iconScale; 122 public int iconSizePx; 123 public int iconTextSizePx; 124 public int iconDrawablePaddingPx; 125 public int iconDrawablePaddingOriginalPx; 126 127 public float cellScaleToFit; 128 public int cellWidthPx; 129 public int cellHeightPx; 130 public int workspaceCellPaddingXPx; 131 132 public int cellYPaddingPx; 133 134 // Folder 135 public float folderLabelTextScale; 136 public int folderLabelTextSizePx; 137 public int folderIconSizePx; 138 public int folderIconOffsetYPx; 139 140 // Folder content 141 public int folderCellLayoutBorderSpacingPx; 142 public int folderContentPaddingLeftRight; 143 public int folderContentPaddingTop; 144 145 // Folder cell 146 public int folderCellWidthPx; 147 public int folderCellHeightPx; 148 149 // Folder child 150 public int folderChildIconSizePx; 151 public int folderChildTextSizePx; 152 public int folderChildDrawablePaddingPx; 153 154 // Hotseat 155 public int hotseatBarSizeExtraSpacePx; 156 public final int numShownHotseatIcons; 157 public int hotseatCellHeightPx; 158 private final int hotseatExtraVerticalSize; 159 // In portrait: size = height, in landscape: size = width 160 public int hotseatBarSizePx; 161 public int hotseatBarTopPaddingPx; 162 public final int hotseatBarBottomPaddingPx; 163 // Start is the side next to the nav bar, end is the side next to the workspace 164 public final int hotseatBarSidePaddingStartPx; 165 public final int hotseatBarSidePaddingEndPx; 166 167 public final float qsbBottomMarginOriginalPx; 168 public int qsbBottomMarginPx; 169 170 // All apps 171 public int allAppsOpenVerticalTranslate; 172 public int allAppsCellHeightPx; 173 public int allAppsCellWidthPx; 174 public int allAppsIconSizePx; 175 public int allAppsIconDrawablePaddingPx; 176 public final int numShownAllAppsColumns; 177 public float allAppsIconTextSizePx; 178 179 // Overview 180 public int overviewTaskMarginPx; 181 public int overviewTaskIconSizePx; 182 public int overviewTaskThumbnailTopMarginPx; 183 public final int overviewActionsMarginThreeButtonPx; 184 public final int overviewActionsMarginGesturePx; 185 186 // Widgets 187 public final PointF appWidgetScale = new PointF(1.0f, 1.0f); 188 189 // Drop Target 190 public int dropTargetBarSizePx; 191 public int dropTargetDragPaddingPx; 192 public int dropTargetTextSizePx; 193 194 // Insets 195 private final Rect mInsets = new Rect(); 196 public final Rect workspacePadding = new Rect(); 197 private final Rect mHotseatPadding = new Rect(); 198 // When true, nav bar is on the left side of the screen. 199 private boolean mIsSeascape; 200 201 // Notification dots 202 public DotRenderer mDotRendererWorkSpace; 203 public DotRenderer mDotRendererAllApps; 204 205 // Taskbar 206 public boolean isTaskbarPresent; 207 public int taskbarSize; 208 // How much of the bottom inset is due to Taskbar rather than other system elements. 209 public int nonOverlappingTaskbarInset; 210 211 // DragController 212 public int flingToDeleteThresholdVelocity; 213 DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds, boolean isMultiWindowMode, boolean transposeLayoutWithOrientation, boolean useTwoPanels)214 DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds, 215 boolean isMultiWindowMode, boolean transposeLayoutWithOrientation, 216 boolean useTwoPanels) { 217 218 this.inv = inv; 219 this.isLandscape = windowBounds.isLandscape(); 220 this.isMultiWindowMode = isMultiWindowMode; 221 this.transposeLayoutWithOrientation = transposeLayoutWithOrientation; 222 windowX = windowBounds.bounds.left; 223 windowY = windowBounds.bounds.top; 224 225 isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode; 226 227 // Determine sizes. 228 widthPx = windowBounds.bounds.width(); 229 heightPx = windowBounds.bounds.height(); 230 availableWidthPx = windowBounds.availableSize.x; 231 int nonFinalAvailableHeightPx = windowBounds.availableSize.y; 232 233 mInfo = info; 234 // If the device's pixel density was scaled (usually via settings for A11y), use the 235 // original dimensions to determine if rotation is allowed of not. 236 float originalSmallestWidth = dpiFromPx(Math.min(widthPx, heightPx), DENSITY_DEVICE_STABLE); 237 allowRotation = originalSmallestWidth >= MIN_TABLET_WIDTH; 238 // Tablet UI does not support emulated landscape. 239 isTablet = allowRotation && info.isTablet(windowBounds); 240 isPhone = !isTablet; 241 isTwoPanels = isTablet && useTwoPanels; 242 243 aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx); 244 boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0; 245 246 // Some more constants 247 context = getContext(context, info, isVerticalBarLayout() 248 ? Configuration.ORIENTATION_LANDSCAPE 249 : Configuration.ORIENTATION_PORTRAIT); 250 mMetrics = context.getResources().getDisplayMetrics(); 251 final Resources res = context.getResources(); 252 253 isTaskbarPresent = isTablet && FeatureFlags.ENABLE_TASKBAR.get(); 254 if (isTaskbarPresent) { 255 // Taskbar will be added later, but provides bottom insets that we should subtract 256 // from availableHeightPx. 257 taskbarSize = res.getDimensionPixelSize(R.dimen.taskbar_size); 258 WindowInsets windowInsets = 259 context.createWindowContext( 260 context.getSystemService(DisplayManager.class).getDisplay(mInfo.id), 261 TYPE_APPLICATION, null) 262 .getSystemService(WindowManager.class) 263 .getCurrentWindowMetrics().getWindowInsets(); 264 nonOverlappingTaskbarInset = taskbarSize - windowInsets.getSystemWindowInsetBottom(); 265 if (nonOverlappingTaskbarInset > 0) { 266 nonFinalAvailableHeightPx -= nonOverlappingTaskbarInset; 267 } 268 } 269 availableHeightPx = nonFinalAvailableHeightPx; 270 271 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin); 272 273 desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : isScalableGrid 274 ? res.getDimensionPixelSize(R.dimen.scalable_grid_left_right_margin) 275 : res.getDimensionPixelSize(R.dimen.dynamic_grid_left_right_margin); 276 desiredWorkspaceLeftRightOriginalPx = desiredWorkspaceLeftRightMarginPx; 277 278 279 allAppsOpenVerticalTranslate = res.getDimensionPixelSize( 280 R.dimen.all_apps_open_vertical_translate); 281 282 folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale); 283 folderContentPaddingLeftRight = 284 res.getDimensionPixelSize(R.dimen.folder_content_padding_left_right); 285 folderContentPaddingTop = res.getDimensionPixelSize(R.dimen.folder_content_padding_top); 286 287 setCellLayoutBorderSpacing(pxFromDp(inv.borderSpacing, mMetrics, 1f)); 288 cellLayoutBorderSpacingOriginalPx = cellLayoutBorderSpacingPx; 289 folderCellLayoutBorderSpacingPx = cellLayoutBorderSpacingPx; 290 291 int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet 292 ? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1; 293 int cellLayoutPadding = isScalableGrid 294 ? 0 295 : res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding); 296 297 if (isTwoPanels) { 298 cellLayoutPaddingLeftRightPx = 299 res.getDimensionPixelSize(R.dimen.two_panel_home_side_padding); 300 cellLayoutBottomPaddingPx = 0; 301 } else if (isLandscape) { 302 cellLayoutPaddingLeftRightPx = 0; 303 cellLayoutBottomPaddingPx = cellLayoutPadding; 304 } else { 305 cellLayoutPaddingLeftRightPx = cellLayoutPaddingLeftRightMultiplier * cellLayoutPadding; 306 cellLayoutBottomPaddingPx = 0; 307 } 308 309 workspacePageIndicatorHeight = res.getDimensionPixelSize( 310 R.dimen.workspace_page_indicator_height); 311 mWorkspacePageIndicatorOverlapWorkspace = 312 res.getDimensionPixelSize(R.dimen.workspace_page_indicator_overlap_workspace); 313 314 iconDrawablePaddingOriginalPx = 315 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding); 316 317 dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size); 318 dropTargetDragPaddingPx = res.getDimensionPixelSize(R.dimen.drop_target_drag_padding); 319 dropTargetTextSizePx = res.getDimensionPixelSize(R.dimen.drop_target_text_size); 320 321 workspaceSpringLoadedBottomSpace = 322 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space); 323 324 workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x); 325 326 numShownHotseatIcons = 327 isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons; 328 numShownAllAppsColumns = 329 isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns; 330 hotseatBarSizeExtraSpacePx = 0; 331 hotseatBarTopPaddingPx = 332 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding); 333 hotseatBarBottomPaddingPx = (isTallDevice ? 0 334 : res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding)) 335 + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding); 336 hotseatBarSidePaddingEndPx = 337 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding); 338 // Add a bit of space between nav bar and hotseat in vertical bar layout. 339 hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0; 340 hotseatExtraVerticalSize = 341 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size); 342 updateHotseatIconSize(pxFromDp(inv.iconSize, mMetrics, 1f)); 343 344 qsbBottomMarginOriginalPx = isScalableGrid 345 ? res.getDimensionPixelSize(R.dimen.scalable_grid_qsb_bottom_margin) 346 : 0; 347 348 overviewTaskMarginPx = res.getDimensionPixelSize(R.dimen.overview_task_margin); 349 overviewTaskIconSizePx = 350 isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get() ? res.getDimensionPixelSize( 351 R.dimen.task_thumbnail_icon_size_grid) : res.getDimensionPixelSize( 352 R.dimen.task_thumbnail_icon_size); 353 overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx * 2; 354 overviewActionsMarginGesturePx = res.getDimensionPixelSize( 355 R.dimen.overview_actions_bottom_margin_gesture); 356 overviewActionsMarginThreeButtonPx = res.getDimensionPixelSize( 357 R.dimen.overview_actions_bottom_margin_three_button); 358 359 // Calculate all of the remaining variables. 360 extraSpace = updateAvailableDimensions(res); 361 362 // Now that we have all of the variables calculated, we can tune certain sizes. 363 if (isScalableGrid && inv.devicePaddings != null) { 364 // Paddings were created assuming no scaling, so we first unscale the extra space. 365 int unscaledExtraSpace = (int) (extraSpace / cellScaleToFit); 366 DevicePadding padding = inv.devicePaddings.getDevicePadding(unscaledExtraSpace); 367 368 int paddingWorkspaceTop = padding.getWorkspaceTopPadding(unscaledExtraSpace); 369 int paddingWorkspaceBottom = padding.getWorkspaceBottomPadding(unscaledExtraSpace); 370 int paddingHotseatBottom = padding.getHotseatBottomPadding(unscaledExtraSpace); 371 372 workspaceTopPadding = Math.round(paddingWorkspaceTop * cellScaleToFit); 373 workspaceBottomPadding = Math.round(paddingWorkspaceBottom * cellScaleToFit); 374 extraHotseatBottomPadding = Math.round(paddingHotseatBottom * cellScaleToFit); 375 376 hotseatBarSizePx += extraHotseatBottomPadding; 377 378 qsbBottomMarginPx = Math.round(qsbBottomMarginOriginalPx * cellScaleToFit); 379 } else if (!isVerticalBarLayout() && isPhone && isTallDevice) { 380 // We increase the hotseat size when there is extra space. 381 382 if (Float.compare(aspectRatio, TALLER_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0 383 && extraSpace >= Utilities.dpToPx(TALL_DEVICE_EXTRA_SPACE_THRESHOLD_DP)) { 384 // For taller devices, we will take a piece of the extra space from each row, 385 // and add it to the space above and below the hotseat. 386 387 // For devices with more extra space, we take a larger piece from each cell. 388 int piece = extraSpace < Utilities.dpToPx(TALL_DEVICE_MORE_EXTRA_SPACE_THRESHOLD_DP) 389 ? 7 : 5; 390 391 int extraSpace = ((getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2) 392 * inv.numRows) / piece; 393 394 workspaceTopPadding = extraSpace / 8; 395 int halfLeftOver = (extraSpace - workspaceTopPadding) / 2; 396 hotseatBarTopPaddingPx += halfLeftOver; 397 hotseatBarSizeExtraSpacePx = halfLeftOver; 398 } else { 399 // ie. For a display with a large aspect ratio, we can keep the icons on the 400 // workspace in portrait mode closer together by adding more height to the hotseat. 401 // Note: This calculation was created after noticing a pattern in the design spec. 402 hotseatBarSizeExtraSpacePx = getCellSize().y - iconSizePx 403 - iconDrawablePaddingPx * 2 - workspacePageIndicatorHeight; 404 } 405 406 updateHotseatIconSize(iconSizePx); 407 408 // Recalculate the available dimensions using the new hotseat size. 409 updateAvailableDimensions(res); 410 } 411 updateWorkspacePadding(); 412 413 flingToDeleteThresholdVelocity = res.getDimensionPixelSize( 414 R.dimen.drag_flingToDeleteMinVelocity); 415 416 // This is done last, after iconSizePx is calculated above. 417 Path dotPath = GraphicsUtils.getShapePath(DEFAULT_DOT_SIZE); 418 mDotRendererWorkSpace = new DotRenderer(iconSizePx, dotPath, DEFAULT_DOT_SIZE); 419 mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace : 420 new DotRenderer(allAppsIconSizePx, dotPath, DEFAULT_DOT_SIZE); 421 } 422 423 private void updateHotseatIconSize(int hotseatIconSizePx) { 424 // Ensure there is enough space for folder icons, which have a slightly larger radius. 425 hotseatCellHeightPx = (int) Math.ceil(hotseatIconSizePx * ICON_OVERLAP_FACTOR); 426 if (isVerticalBarLayout()) { 427 hotseatBarSizePx = hotseatIconSizePx + hotseatBarSidePaddingStartPx 428 + hotseatBarSidePaddingEndPx; 429 } else { 430 hotseatBarSizePx = hotseatIconSizePx + hotseatBarTopPaddingPx 431 + hotseatBarBottomPaddingPx + (isScalableGrid ? 0 : hotseatExtraVerticalSize) 432 + hotseatBarSizeExtraSpacePx; 433 } 434 } 435 436 private void setCellLayoutBorderSpacing(int borderSpacing) { 437 cellLayoutBorderSpacingPx = isScalableGrid ? borderSpacing : 0; 438 } 439 440 /** 441 * We inset the widget padding added by the system and instead rely on the border spacing 442 * between cells to create reliable consistency between widgets 443 */ 444 public boolean shouldInsetWidgets() { 445 Rect widgetPadding = inv.defaultWidgetPadding; 446 447 // Check all sides to ensure that the widget won't overlap into another cell, or into 448 // status bar. 449 return workspaceTopPadding > widgetPadding.top 450 && cellLayoutBorderSpacingPx > widgetPadding.left 451 && cellLayoutBorderSpacingPx > widgetPadding.top 452 && cellLayoutBorderSpacingPx > widgetPadding.right 453 && cellLayoutBorderSpacingPx > widgetPadding.bottom; 454 } 455 toBuilder(Context context)456 public Builder toBuilder(Context context) { 457 WindowBounds bounds = 458 new WindowBounds(widthPx, heightPx, availableWidthPx, availableHeightPx); 459 bounds.bounds.offsetTo(windowX, windowY); 460 return new Builder(context, inv, mInfo) 461 .setWindowBounds(bounds) 462 .setUseTwoPanels(isTwoPanels) 463 .setMultiWindowMode(isMultiWindowMode); 464 } 465 copy(Context context)466 public DeviceProfile copy(Context context) { 467 return toBuilder(context).build(); 468 } 469 470 /** 471 * TODO: Move this to the builder as part of setMultiWindowMode 472 */ getMultiWindowProfile(Context context, WindowBounds windowBounds)473 public DeviceProfile getMultiWindowProfile(Context context, WindowBounds windowBounds) { 474 DeviceProfile profile = toBuilder(context) 475 .setWindowBounds(windowBounds) 476 .setMultiWindowMode(true) 477 .build(); 478 479 profile.hideWorkspaceLabelsIfNotEnoughSpace(); 480 481 // We use these scales to measure and layout the widgets using their full invariant profile 482 // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans. 483 float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x; 484 float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y; 485 profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY); 486 profile.updateWorkspacePadding(); 487 488 return profile; 489 } 490 491 /** 492 * Checks if there is enough space for labels on the workspace. 493 * If there is not, labels on the Workspace are hidden. 494 * It is important to call this method after the All Apps variables have been set. 495 */ hideWorkspaceLabelsIfNotEnoughSpace()496 private void hideWorkspaceLabelsIfNotEnoughSpace() { 497 float iconTextHeight = Utilities.calculateTextHeight(iconTextSizePx); 498 float workspaceCellPaddingY = getCellSize().y - iconSizePx - iconDrawablePaddingPx 499 - iconTextHeight; 500 501 // We want enough space so that the text is closer to its corresponding icon. 502 if (workspaceCellPaddingY < iconTextHeight) { 503 iconTextSizePx = 0; 504 iconDrawablePaddingPx = 0; 505 cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR); 506 autoResizeAllAppsCells(); 507 } 508 } 509 510 /** 511 * Re-computes the all-apps cell size to be independent of workspace 512 */ autoResizeAllAppsCells()513 public void autoResizeAllAppsCells() { 514 int textHeight = Utilities.calculateTextHeight(allAppsIconTextSizePx); 515 int topBottomPadding = textHeight; 516 allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx 517 + textHeight + (topBottomPadding * 2); 518 } 519 520 /** 521 * Returns the amount of extra (or unused) vertical space. 522 */ updateAvailableDimensions(Resources res)523 private int updateAvailableDimensions(Resources res) { 524 updateIconSize(1f, res); 525 526 Point workspacePadding = getTotalWorkspacePadding(); 527 528 // Check to see if the icons fit within the available height. 529 float usedHeight = getCellLayoutHeight(); 530 final int maxHeight = availableHeightPx - workspacePadding.y; 531 float extraHeight = Math.max(0, maxHeight - usedHeight); 532 float scaleY = maxHeight / usedHeight; 533 boolean shouldScale = scaleY < 1f; 534 535 float scaleX = 1f; 536 if (isScalableGrid) { 537 // We scale to fit the cellWidth and cellHeight in the available space. 538 // The benefit of scalable grids is that we can get consistent aspect ratios between 539 // devices. 540 float usedWidth = (cellWidthPx * inv.numColumns) 541 + (cellLayoutBorderSpacingPx * (inv.numColumns - 1)) 542 + (desiredWorkspaceLeftRightMarginPx * 2); 543 // We do not subtract padding here, as we also scale the workspace padding if needed. 544 scaleX = availableWidthPx / usedWidth; 545 shouldScale = true; 546 } 547 548 if (shouldScale) { 549 float scale = Math.min(scaleX, scaleY); 550 updateIconSize(scale, res); 551 extraHeight = Math.max(0, maxHeight - getCellLayoutHeight()); 552 } 553 554 updateAvailableFolderCellDimensions(res); 555 return Math.round(extraHeight); 556 } 557 558 private int getCellLayoutHeight() { 559 return (cellHeightPx * inv.numRows) + (cellLayoutBorderSpacingPx * (inv.numRows - 1)); 560 } 561 562 /** 563 * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx, 564 * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants, 565 * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx. 566 */ 567 public void updateIconSize(float scale, Resources res) { 568 // Icon scale should never exceed 1, otherwise pixellation may occur. 569 iconScale = Math.min(1f, scale); 570 cellScaleToFit = scale; 571 572 573 // Workspace 574 final boolean isVerticalLayout = isVerticalBarLayout(); 575 float invIconSizeDp = isLandscape ? inv.landscapeIconSize : inv.iconSize; 576 iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, iconScale)); 577 float invIconTextSizeSp = isLandscape ? inv.landscapeIconTextSize : inv.iconTextSize; 578 iconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * iconScale); 579 iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * iconScale); 580 581 setCellLayoutBorderSpacing((int) (cellLayoutBorderSpacingOriginalPx * scale)); 582 583 if (isScalableGrid) { 584 cellWidthPx = pxFromDp(inv.minCellWidth, mMetrics, scale); 585 cellHeightPx = pxFromDp(inv.minCellHeight, mMetrics, scale); 586 int cellContentHeight = iconSizePx + iconDrawablePaddingPx 587 + Utilities.calculateTextHeight(iconTextSizePx); 588 cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2; 589 desiredWorkspaceLeftRightMarginPx = (int) (desiredWorkspaceLeftRightOriginalPx * scale); 590 } else { 591 cellWidthPx = iconSizePx + iconDrawablePaddingPx; 592 cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR) 593 + iconDrawablePaddingPx 594 + Utilities.calculateTextHeight(iconTextSizePx); 595 int cellPaddingY = (getCellSize().y - cellHeightPx) / 2; 596 if (iconDrawablePaddingPx > cellPaddingY && !isVerticalLayout 597 && !isMultiWindowMode) { 598 // Ensures that the label is closer to its corresponding icon. This is not an issue 599 // with vertical bar layout or multi-window mode since the issue is handled 600 // separately with their calls to {@link #adjustToHideWorkspaceLabels}. 601 cellHeightPx -= (iconDrawablePaddingPx - cellPaddingY); 602 iconDrawablePaddingPx = cellPaddingY; 603 } 604 } 605 606 // All apps 607 if (numShownAllAppsColumns != inv.numColumns) { 608 allAppsIconSizePx = pxFromDp(inv.allAppsIconSize, mMetrics); 609 allAppsIconTextSizePx = pxFromSp(inv.allAppsIconTextSize, mMetrics); 610 allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx; 611 autoResizeAllAppsCells(); 612 } else { 613 allAppsIconSizePx = iconSizePx; 614 allAppsIconTextSizePx = iconTextSizePx; 615 allAppsIconDrawablePaddingPx = iconDrawablePaddingPx; 616 allAppsCellHeightPx = getCellSize().y; 617 } 618 allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx; 619 620 if (isVerticalLayout) { 621 hideWorkspaceLabelsIfNotEnoughSpace(); 622 } 623 624 // Hotseat 625 updateHotseatIconSize(iconSizePx); 626 627 if (!isVerticalLayout) { 628 int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx 629 - workspacePageIndicatorHeight - edgeMarginPx; 630 float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace; 631 workspaceSpringLoadShrinkFactor = Math.min( 632 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f, 633 1 - (minRequiredHeight / expectedWorkspaceHeight)); 634 } else { 635 workspaceSpringLoadShrinkFactor = 636 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f; 637 } 638 639 // Folder icon 640 folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx); 641 folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2; 642 } 643 updateAvailableFolderCellDimensions(Resources res)644 private void updateAvailableFolderCellDimensions(Resources res) { 645 updateFolderCellSize(1f, res); 646 647 final int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_height); 648 649 // Don't let the folder get too close to the edges of the screen. 650 int folderMargin = edgeMarginPx * 2; 651 Point totalWorkspacePadding = getTotalWorkspacePadding(); 652 653 // Check if the icons fit within the available height. 654 float contentUsedHeight = folderCellHeightPx * inv.numFolderRows 655 + ((inv.numFolderRows - 1) * folderCellLayoutBorderSpacingPx); 656 int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize 657 - folderMargin - folderContentPaddingTop; 658 float scaleY = contentMaxHeight / contentUsedHeight; 659 660 // Check if the icons fit within the available width. 661 float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns 662 + ((inv.numFolderColumns - 1) * folderCellLayoutBorderSpacingPx); 663 int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin 664 - folderContentPaddingLeftRight * 2; 665 float scaleX = contentMaxWidth / contentUsedWidth; 666 667 float scale = Math.min(scaleX, scaleY); 668 if (scale < 1f) { 669 updateFolderCellSize(scale, res); 670 } 671 } 672 updateFolderCellSize(float scale, Resources res)673 private void updateFolderCellSize(float scale, Resources res) { 674 float invIconSizeDp = isVerticalBarLayout() ? inv.landscapeIconSize : inv.iconSize; 675 folderChildIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale)); 676 folderChildTextSizePx = pxFromSp(inv.iconTextSize, mMetrics, scale); 677 folderLabelTextSizePx = (int) (folderChildTextSizePx * folderLabelTextScale); 678 679 int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx); 680 681 if (isScalableGrid) { 682 int minWidth = folderChildIconSizePx + iconDrawablePaddingPx * 2; 683 int minHeight = folderChildIconSizePx + iconDrawablePaddingPx * 2 + textHeight; 684 685 folderCellWidthPx = (int) Math.max(minWidth, cellWidthPx * scale); 686 folderCellHeightPx = (int) Math.max(minHeight, cellHeightPx * scale); 687 688 int borderSpacing = (int) (cellLayoutBorderSpacingOriginalPx * scale); 689 folderCellLayoutBorderSpacingPx = borderSpacing; 690 folderContentPaddingLeftRight = borderSpacing; 691 folderContentPaddingTop = borderSpacing; 692 } else { 693 int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) 694 * scale); 695 int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) 696 * scale); 697 698 folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX; 699 folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight; 700 } 701 702 folderChildDrawablePaddingPx = Math.max(0, 703 (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3); 704 } 705 updateInsets(Rect insets)706 public void updateInsets(Rect insets) { 707 mInsets.set(insets); 708 updateWorkspacePadding(); 709 } 710 711 /** 712 * The current device insets. This is generally same as the insets being dispatched to 713 * {@link Insettable} elements, but can differ if the element is using a different profile. 714 */ getInsets()715 public Rect getInsets() { 716 return mInsets; 717 } 718 getCellSize()719 public Point getCellSize() { 720 return getCellSize(null); 721 } 722 getCellSize(Point result)723 public Point getCellSize(Point result) { 724 if (result == null) { 725 result = new Point(); 726 } 727 // Since we are only concerned with the overall padding, layout direction does 728 // not matter. 729 Point padding = getTotalWorkspacePadding(); 730 result.x = calculateCellWidth(availableWidthPx - padding.x 731 - cellLayoutPaddingLeftRightPx * 2, cellLayoutBorderSpacingPx, inv.numColumns); 732 result.y = calculateCellHeight(availableHeightPx - padding.y 733 - cellLayoutBottomPaddingPx, cellLayoutBorderSpacingPx, inv.numRows); 734 return result; 735 } 736 getTotalWorkspacePadding()737 public Point getTotalWorkspacePadding() { 738 updateWorkspacePadding(); 739 return new Point(workspacePadding.left + workspacePadding.right, 740 workspacePadding.top + workspacePadding.bottom); 741 } 742 743 /** 744 * Updates {@link #workspacePadding} as a result of any internal value change to reflect the 745 * new workspace padding 746 */ updateWorkspacePadding()747 private void updateWorkspacePadding() { 748 Rect padding = workspacePadding; 749 if (isVerticalBarLayout()) { 750 padding.top = 0; 751 padding.bottom = edgeMarginPx; 752 if (isSeascape()) { 753 padding.left = hotseatBarSizePx; 754 padding.right = hotseatBarSidePaddingStartPx; 755 } else { 756 padding.left = hotseatBarSidePaddingStartPx; 757 padding.right = hotseatBarSizePx; 758 } 759 } else { 760 int hotseatTop = isTaskbarPresent ? taskbarSize : hotseatBarSizePx; 761 int paddingBottom = hotseatTop + workspacePageIndicatorHeight 762 + workspaceBottomPadding - mWorkspacePageIndicatorOverlapWorkspace; 763 if (isTablet) { 764 // Pad the left and right of the workspace to ensure consistent spacing 765 // between all icons 766 // The amount of screen space available for left/right padding. 767 int availablePaddingX = Math.max(0, widthPx - ((inv.numColumns * cellWidthPx) + 768 ((inv.numColumns - 1) * cellWidthPx))); 769 availablePaddingX = (int) Math.min(availablePaddingX, 770 widthPx * MAX_HORIZONTAL_PADDING_PERCENT); 771 int hotseatVerticalPadding = isTaskbarPresent ? 0 772 : hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx; 773 int availablePaddingY = Math.max(0, heightPx - edgeMarginPx - paddingBottom 774 - (2 * inv.numRows * cellHeightPx) - hotseatVerticalPadding); 775 padding.set(availablePaddingX / 2, edgeMarginPx + availablePaddingY / 2, 776 availablePaddingX / 2, paddingBottom + availablePaddingY / 2); 777 778 if (isTwoPanels) { 779 padding.set(0, padding.top, 0, padding.bottom); 780 } 781 } else { 782 // Pad the top and bottom of the workspace with search/hotseat bar sizes 783 padding.set(desiredWorkspaceLeftRightMarginPx, 784 workspaceTopPadding + (isScalableGrid ? 0 : edgeMarginPx), 785 desiredWorkspaceLeftRightMarginPx, 786 paddingBottom); 787 } 788 } 789 } 790 getHotseatLayoutPadding()791 public Rect getHotseatLayoutPadding() { 792 if (isVerticalBarLayout()) { 793 if (isSeascape()) { 794 mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx, 795 mInsets.top, hotseatBarSidePaddingEndPx, mInsets.bottom); 796 } else { 797 mHotseatPadding.set(hotseatBarSidePaddingEndPx, mInsets.top, 798 mInsets.right + hotseatBarSidePaddingStartPx, mInsets.bottom); 799 } 800 } else { 801 // We want the edges of the hotseat to line up with the edges of the workspace, but the 802 // icons in the hotseat are a different size, and so don't line up perfectly. To account 803 // for this, we pad the left and right of the hotseat with half of the difference of a 804 // workspace cell vs a hotseat cell. 805 float workspaceCellWidth = (float) widthPx / inv.numColumns; 806 float hotseatCellWidth = (float) widthPx / numShownHotseatIcons; 807 int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2); 808 mHotseatPadding.set( 809 hotseatAdjustment + workspacePadding.left + cellLayoutPaddingLeftRightPx 810 + mInsets.left, 811 hotseatBarTopPaddingPx, 812 hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx 813 + mInsets.right, 814 hotseatBarSizePx - hotseatCellHeightPx - hotseatBarTopPaddingPx 815 + cellLayoutBottomPaddingPx + mInsets.bottom); 816 } 817 return mHotseatPadding; 818 } 819 820 /** 821 * @return the bounds for which the open folders should be contained within 822 */ getAbsoluteOpenFolderBounds()823 public Rect getAbsoluteOpenFolderBounds() { 824 if (isVerticalBarLayout()) { 825 // Folders should only appear right of the drop target bar and left of the hotseat 826 return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx, 827 mInsets.top, 828 mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx, 829 mInsets.top + availableHeightPx); 830 } else { 831 // Folders should only appear below the drop target bar and above the hotseat 832 int hotseatTop = isTaskbarPresent ? taskbarSize : hotseatBarSizePx; 833 return new Rect(mInsets.left + edgeMarginPx, 834 mInsets.top + dropTargetBarSizePx + edgeMarginPx, 835 mInsets.left + availableWidthPx - edgeMarginPx, 836 mInsets.top + availableHeightPx - hotseatTop 837 - workspacePageIndicatorHeight - edgeMarginPx); 838 } 839 } 840 calculateCellWidth(int width, int borderSpacing, int countX)841 public static int calculateCellWidth(int width, int borderSpacing, int countX) { 842 return (width - ((countX - 1) * borderSpacing)) / countX; 843 } calculateCellHeight(int height, int borderSpacing, int countY)844 public static int calculateCellHeight(int height, int borderSpacing, int countY) { 845 return (height - ((countY - 1) * borderSpacing)) / countY; 846 } 847 848 /** 849 * When {@code true}, the device is in landscape mode and the hotseat is on the right column. 850 * When {@code false}, either device is in portrait mode or the device is in landscape mode and 851 * the hotseat is on the bottom row. 852 */ isVerticalBarLayout()853 public boolean isVerticalBarLayout() { 854 return isLandscape && transposeLayoutWithOrientation; 855 } 856 857 /** 858 * Updates orientation information and returns true if it has changed from the previous value. 859 */ updateIsSeascape(Context context)860 public boolean updateIsSeascape(Context context) { 861 if (isVerticalBarLayout()) { 862 boolean isSeascape = DisplayController.INSTANCE.get(context) 863 .getInfo().rotation == Surface.ROTATION_270; 864 if (mIsSeascape != isSeascape) { 865 mIsSeascape = isSeascape; 866 return true; 867 } 868 } 869 return false; 870 } 871 isSeascape()872 public boolean isSeascape() { 873 return isVerticalBarLayout() && mIsSeascape; 874 } 875 shouldFadeAdjacentWorkspaceScreens()876 public boolean shouldFadeAdjacentWorkspaceScreens() { 877 return isVerticalBarLayout(); 878 } 879 getCellContentHeight(@ontainerType int containerType)880 public int getCellContentHeight(@ContainerType int containerType) { 881 switch (containerType) { 882 case CellLayout.WORKSPACE: 883 return cellHeightPx; 884 case CellLayout.FOLDER: 885 return folderCellHeightPx; 886 case CellLayout.HOTSEAT: 887 // The hotseat is the only container where the cell height is going to be 888 // different from the content within that cell. 889 return iconSizePx; 890 default: 891 // ?? 892 return 0; 893 } 894 } 895 pxToDpStr(String name, float value)896 private String pxToDpStr(String name, float value) { 897 return "\t" + name + ": " + value + "px (" + dpiFromPx(value, mMetrics.densityDpi) + "dp)"; 898 } 899 dump(String prefix, PrintWriter writer)900 public void dump(String prefix, PrintWriter writer) { 901 writer.println(prefix + "DeviceProfile:"); 902 writer.println(prefix + "\t1 dp = " + mMetrics.density + " px"); 903 904 writer.println(prefix + "\tallowRotation:" + allowRotation); 905 writer.println(prefix + "\tisTablet:" + isTablet); 906 writer.println(prefix + "\tisPhone:" + isPhone); 907 writer.println(prefix + "\ttransposeLayoutWithOrientation:" 908 + transposeLayoutWithOrientation); 909 910 writer.println(prefix + "\tisLandscape:" + isLandscape); 911 writer.println(prefix + "\tisMultiWindowMode:" + isMultiWindowMode); 912 writer.println(prefix + "\tisTwoPanels:" + isTwoPanels); 913 914 writer.println(prefix + pxToDpStr("windowX", windowX)); 915 writer.println(prefix + pxToDpStr("windowY", windowY)); 916 writer.println(prefix + pxToDpStr("widthPx", widthPx)); 917 writer.println(prefix + pxToDpStr("heightPx", heightPx)); 918 919 writer.println(prefix + pxToDpStr("availableWidthPx", availableWidthPx)); 920 writer.println(prefix + pxToDpStr("availableHeightPx", availableHeightPx)); 921 922 writer.println(prefix + "\taspectRatio:" + aspectRatio); 923 924 writer.println(prefix + "\tisScalableGrid:" + isScalableGrid); 925 926 writer.println(prefix + "\tinv.minCellWidth:" + inv.minCellWidth + "dp"); 927 writer.println(prefix + "\tinv.minCellHeight:" + inv.minCellHeight + "dp"); 928 929 writer.println(prefix + "\tinv.numColumns:" + inv.numColumns); 930 writer.println(prefix + "\tinv.numRows:" + inv.numRows); 931 932 writer.println(prefix + pxToDpStr("cellWidthPx", cellWidthPx)); 933 writer.println(prefix + pxToDpStr("cellHeightPx", cellHeightPx)); 934 935 writer.println(prefix + pxToDpStr("getCellSize().x", getCellSize().x)); 936 writer.println(prefix + pxToDpStr("getCellSize().y", getCellSize().y)); 937 938 writer.println(prefix + "\tinv.iconSize:" + inv.iconSize + "dp"); 939 writer.println(prefix + pxToDpStr("iconSizePx", iconSizePx)); 940 writer.println(prefix + pxToDpStr("iconTextSizePx", iconTextSizePx)); 941 writer.println(prefix + pxToDpStr("iconDrawablePaddingPx", iconDrawablePaddingPx)); 942 943 writer.println(prefix + pxToDpStr("folderCellWidthPx", folderCellWidthPx)); 944 writer.println(prefix + pxToDpStr("folderCellHeightPx", folderCellHeightPx)); 945 writer.println(prefix + pxToDpStr("folderChildIconSizePx", folderChildIconSizePx)); 946 writer.println(prefix + pxToDpStr("folderChildTextSizePx", folderChildTextSizePx)); 947 writer.println(prefix + pxToDpStr("folderChildDrawablePaddingPx", 948 folderChildDrawablePaddingPx)); 949 writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacingPx", 950 folderCellLayoutBorderSpacingPx)); 951 952 writer.println(prefix + pxToDpStr("cellLayoutBorderSpacingPx", 953 cellLayoutBorderSpacingPx)); 954 writer.println(prefix + pxToDpStr("desiredWorkspaceLeftRightMarginPx", 955 desiredWorkspaceLeftRightMarginPx)); 956 957 writer.println(prefix + pxToDpStr("allAppsIconSizePx", allAppsIconSizePx)); 958 writer.println(prefix + pxToDpStr("allAppsIconTextSizePx", allAppsIconTextSizePx)); 959 writer.println(prefix + pxToDpStr("allAppsIconDrawablePaddingPx", 960 allAppsIconDrawablePaddingPx)); 961 writer.println(prefix + pxToDpStr("allAppsCellHeightPx", allAppsCellHeightPx)); 962 writer.println(prefix + "\tnumShownAllAppsColumns: " + numShownAllAppsColumns); 963 964 writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx)); 965 writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx)); 966 writer.println(prefix + pxToDpStr("hotseatBarTopPaddingPx", hotseatBarTopPaddingPx)); 967 writer.println(prefix + pxToDpStr("hotseatBarBottomPaddingPx", hotseatBarBottomPaddingPx)); 968 writer.println(prefix + pxToDpStr("hotseatBarSidePaddingStartPx", 969 hotseatBarSidePaddingStartPx)); 970 writer.println(prefix + pxToDpStr("hotseatBarSidePaddingEndPx", 971 hotseatBarSidePaddingEndPx)); 972 writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons); 973 974 writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent); 975 976 writer.println(prefix + pxToDpStr("taskbarSize", taskbarSize)); 977 writer.println(prefix + pxToDpStr("nonOverlappingTaskbarInset", 978 nonOverlappingTaskbarInset)); 979 980 writer.println(prefix + pxToDpStr("workspacePadding.left", workspacePadding.left)); 981 writer.println(prefix + pxToDpStr("workspacePadding.top", workspacePadding.top)); 982 writer.println(prefix + pxToDpStr("workspacePadding.right", workspacePadding.right)); 983 writer.println(prefix + pxToDpStr("workspacePadding.bottom", workspacePadding.bottom)); 984 985 writer.println(prefix + pxToDpStr("iconScale", iconScale)); 986 writer.println(prefix + pxToDpStr("cellScaleToFit ", cellScaleToFit)); 987 writer.println(prefix + pxToDpStr("extraSpace", extraSpace)); 988 989 if (inv.devicePaddings != null) { 990 int unscaledExtraSpace = (int) (extraSpace / iconScale); 991 writer.println(prefix + pxToDpStr("maxEmptySpace", 992 inv.devicePaddings.getDevicePadding(unscaledExtraSpace).getMaxEmptySpacePx())); 993 } 994 writer.println(prefix + pxToDpStr("workspaceTopPadding", workspaceTopPadding)); 995 writer.println(prefix + pxToDpStr("workspaceBottomPadding", workspaceBottomPadding)); 996 writer.println(prefix + pxToDpStr("extraHotseatBottomPadding", extraHotseatBottomPadding)); 997 } 998 getContext(Context c, Info info, int orientation)999 private static Context getContext(Context c, Info info, int orientation) { 1000 Configuration config = new Configuration(c.getResources().getConfiguration()); 1001 config.orientation = orientation; 1002 config.densityDpi = info.densityDpi; 1003 return c.createConfigurationContext(config); 1004 } 1005 1006 /** 1007 * Callback when a component changes the DeviceProfile associated with it, as a result of 1008 * configuration change 1009 */ 1010 public interface OnDeviceProfileChangeListener { 1011 1012 /** 1013 * Called when the device profile is reassigned. Note that for layout and measurements, it 1014 * is sufficient to listen for inset changes. Use this callback when you need to perform 1015 * a one time operation. 1016 */ 1017 void onDeviceProfileChanged(DeviceProfile dp); 1018 } 1019 1020 public static class Builder { 1021 private Context mContext; 1022 private InvariantDeviceProfile mInv; 1023 private Info mInfo; 1024 1025 private WindowBounds mWindowBounds; 1026 private boolean mUseTwoPanels; 1027 1028 private boolean mIsMultiWindowMode = false; 1029 private Boolean mTransposeLayoutWithOrientation; 1030 Builder(Context context, InvariantDeviceProfile inv, Info info)1031 public Builder(Context context, InvariantDeviceProfile inv, Info info) { 1032 mContext = context; 1033 mInv = inv; 1034 mInfo = info; 1035 } 1036 setMultiWindowMode(boolean isMultiWindowMode)1037 public Builder setMultiWindowMode(boolean isMultiWindowMode) { 1038 mIsMultiWindowMode = isMultiWindowMode; 1039 return this; 1040 } 1041 setUseTwoPanels(boolean useTwoPanels)1042 public Builder setUseTwoPanels(boolean useTwoPanels) { 1043 mUseTwoPanels = useTwoPanels; 1044 return this; 1045 } 1046 1047 setWindowBounds(WindowBounds bounds)1048 public Builder setWindowBounds(WindowBounds bounds) { 1049 mWindowBounds = bounds; 1050 return this; 1051 } 1052 setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation)1053 public Builder setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation) { 1054 mTransposeLayoutWithOrientation = transposeLayoutWithOrientation; 1055 return this; 1056 } 1057 build()1058 public DeviceProfile build() { 1059 if (mWindowBounds == null) { 1060 throw new IllegalArgumentException("Window bounds not set"); 1061 } 1062 if (mTransposeLayoutWithOrientation == null) { 1063 mTransposeLayoutWithOrientation = !mInfo.isTablet(mWindowBounds); 1064 } 1065 return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, 1066 mIsMultiWindowMode, mTransposeLayoutWithOrientation, mUseTwoPanels); 1067 } 1068 } 1069 1070 } 1071