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 android.content.Context; 20 import android.content.res.Configuration; 21 import android.content.res.Resources; 22 import android.graphics.Point; 23 import android.graphics.PointF; 24 import android.graphics.Rect; 25 import android.view.Surface; 26 27 import com.android.launcher3.CellLayout.ContainerType; 28 import com.android.launcher3.graphics.IconShape; 29 import com.android.launcher3.icons.DotRenderer; 30 import com.android.launcher3.icons.IconNormalizer; 31 import com.android.launcher3.util.DefaultDisplay; 32 import com.android.launcher3.util.WindowBounds; 33 34 public class DeviceProfile { 35 36 private static final float TABLET_MIN_DPS = 600; 37 private static final float LARGE_TABLET_MIN_DPS = 720; 38 39 40 public final InvariantDeviceProfile inv; 41 private final DefaultDisplay.Info mInfo; 42 43 // Device properties 44 public final boolean isTablet; 45 public final boolean isLargeTablet; 46 public final boolean isPhone; 47 public final boolean transposeLayoutWithOrientation; 48 49 // Device properties in current orientation 50 public final boolean isLandscape; 51 public final boolean isMultiWindowMode; 52 53 public final int windowX; 54 public final int windowY; 55 public final int widthPx; 56 public final int heightPx; 57 public final int availableWidthPx; 58 public final int availableHeightPx; 59 60 public final float aspectRatio; 61 62 /** 63 * The maximum amount of left/right workspace padding as a percentage of the screen width. 64 * To be clear, this means that up to 7% of the screen width can be used as left padding, and 65 * 7% of the screen width can be used as right padding. 66 */ 67 private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f; 68 69 private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f; 70 71 // To evenly space the icons, increase the left/right margins for tablets in portrait mode. 72 private static final int PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER = 4; 73 74 // Workspace 75 public final int desiredWorkspaceLeftRightMarginPx; 76 public final int cellLayoutPaddingLeftRightPx; 77 public final int cellLayoutBottomPaddingPx; 78 public final int edgeMarginPx; 79 public float workspaceSpringLoadShrinkFactor; 80 public final int workspaceSpringLoadedBottomSpace; 81 82 // Workspace page indicator 83 public final int workspacePageIndicatorHeight; 84 private final int mWorkspacePageIndicatorOverlapWorkspace; 85 86 // Workspace icons 87 public int iconSizePx; 88 public int iconTextSizePx; 89 public int iconDrawablePaddingPx; 90 public int iconDrawablePaddingOriginalPx; 91 92 public int cellWidthPx; 93 public int cellHeightPx; 94 public int workspaceCellPaddingXPx; 95 96 // Folder 97 public int folderIconSizePx; 98 public int folderIconOffsetYPx; 99 100 // Folder cell 101 public int folderCellWidthPx; 102 public int folderCellHeightPx; 103 104 // Folder child 105 public int folderChildIconSizePx; 106 public int folderChildTextSizePx; 107 public int folderChildDrawablePaddingPx; 108 109 // Hotseat 110 public int hotseatCellHeightPx; 111 // In portrait: size = height, in landscape: size = width 112 public int hotseatBarSizePx; 113 public final int hotseatBarTopPaddingPx; 114 public int hotseatBarBottomPaddingPx; 115 // Start is the side next to the nav bar, end is the side next to the workspace 116 public final int hotseatBarSidePaddingStartPx; 117 public final int hotseatBarSidePaddingEndPx; 118 119 // All apps 120 public int allAppsCellHeightPx; 121 public int allAppsCellWidthPx; 122 public int allAppsIconSizePx; 123 public int allAppsIconDrawablePaddingPx; 124 public float allAppsIconTextSizePx; 125 126 // Widgets 127 public final PointF appWidgetScale = new PointF(1.0f, 1.0f); 128 129 // Drop Target 130 public int dropTargetBarSizePx; 131 132 // Insets 133 private final Rect mInsets = new Rect(); 134 public final Rect workspacePadding = new Rect(); 135 private final Rect mHotseatPadding = new Rect(); 136 // When true, nav bar is on the left side of the screen. 137 private boolean mIsSeascape; 138 139 // Notification dots 140 public DotRenderer mDotRendererWorkSpace; 141 public DotRenderer mDotRendererAllApps; 142 DeviceProfile(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info, Point minSize, Point maxSize, int width, int height, boolean isLandscape, boolean isMultiWindowMode, boolean transposeLayoutWithOrientation, Point windowPosition)143 DeviceProfile(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info, 144 Point minSize, Point maxSize, int width, int height, boolean isLandscape, 145 boolean isMultiWindowMode, boolean transposeLayoutWithOrientation, 146 Point windowPosition) { 147 148 this.inv = inv; 149 this.isLandscape = isLandscape; 150 this.isMultiWindowMode = isMultiWindowMode; 151 windowX = windowPosition.x; 152 windowY = windowPosition.y; 153 154 // Determine sizes. 155 widthPx = width; 156 heightPx = height; 157 if (isLandscape) { 158 availableWidthPx = maxSize.x; 159 availableHeightPx = minSize.y; 160 } else { 161 availableWidthPx = minSize.x; 162 availableHeightPx = maxSize.y; 163 } 164 165 mInfo = info; 166 167 // Constants from resources 168 float swDPs = Utilities.dpiFromPx( 169 Math.min(info.smallestSize.x, info.smallestSize.y), info.metrics); 170 isTablet = swDPs >= TABLET_MIN_DPS; 171 isLargeTablet = swDPs >= LARGE_TABLET_MIN_DPS; 172 isPhone = !isTablet && !isLargeTablet; 173 aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx); 174 boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0; 175 176 // Some more constants 177 this.transposeLayoutWithOrientation = transposeLayoutWithOrientation; 178 179 context = getContext(context, info, isVerticalBarLayout() 180 ? Configuration.ORIENTATION_LANDSCAPE 181 : Configuration.ORIENTATION_PORTRAIT); 182 final Resources res = context.getResources(); 183 184 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin); 185 desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx; 186 187 int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet 188 ? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1; 189 int cellLayoutPadding = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding); 190 if (isLandscape) { 191 cellLayoutPaddingLeftRightPx = 0; 192 cellLayoutBottomPaddingPx = cellLayoutPadding; 193 } else { 194 cellLayoutPaddingLeftRightPx = cellLayoutPaddingLeftRightMultiplier * cellLayoutPadding; 195 cellLayoutBottomPaddingPx = 0; 196 } 197 198 workspacePageIndicatorHeight = res.getDimensionPixelSize( 199 R.dimen.workspace_page_indicator_height); 200 mWorkspacePageIndicatorOverlapWorkspace = 201 res.getDimensionPixelSize(R.dimen.workspace_page_indicator_overlap_workspace); 202 203 iconDrawablePaddingOriginalPx = 204 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding); 205 dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size); 206 workspaceSpringLoadedBottomSpace = 207 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space); 208 209 workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x); 210 211 hotseatBarTopPaddingPx = 212 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding); 213 hotseatBarBottomPaddingPx = (isTallDevice ? 0 214 : res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding)) 215 + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding); 216 hotseatBarSidePaddingEndPx = 217 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding); 218 // Add a bit of space between nav bar and hotseat in vertical bar layout. 219 hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0; 220 hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics) 221 + (isVerticalBarLayout() 222 ? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx) 223 : (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size) 224 + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx)); 225 226 // Calculate all of the remaining variables. 227 updateAvailableDimensions(res); 228 229 // Now that we have all of the variables calculated, we can tune certain sizes. 230 if (!isVerticalBarLayout() && isPhone && isTallDevice) { 231 // We increase the hotseat size when there is extra space. 232 // ie. For a display with a large aspect ratio, we can keep the icons on the workspace 233 // in portrait mode closer together by adding more height to the hotseat. 234 // Note: This calculation was created after noticing a pattern in the design spec. 235 int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2 236 - workspacePageIndicatorHeight; 237 hotseatBarSizePx += extraSpace; 238 hotseatBarBottomPaddingPx += extraSpace; 239 240 // Recalculate the available dimensions using the new hotseat size. 241 updateAvailableDimensions(res); 242 } 243 updateWorkspacePadding(); 244 245 // This is done last, after iconSizePx is calculated above. 246 mDotRendererWorkSpace = new DotRenderer(iconSizePx, IconShape.getShapePath(), 247 IconShape.DEFAULT_PATH_SIZE); 248 mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace : 249 new DotRenderer(allAppsIconSizePx, IconShape.getShapePath(), 250 IconShape.DEFAULT_PATH_SIZE); 251 } 252 toBuilder(Context context)253 public Builder toBuilder(Context context) { 254 Point size = new Point(availableWidthPx, availableHeightPx); 255 return new Builder(context, inv, mInfo) 256 .setSizeRange(size, size) 257 .setSize(widthPx, heightPx) 258 .setWindowPosition(windowX, windowY) 259 .setMultiWindowMode(isMultiWindowMode); 260 } 261 copy(Context context)262 public DeviceProfile copy(Context context) { 263 return toBuilder(context).build(); 264 } 265 266 /** 267 * TODO: Move this to the builder as part of setMultiWindowMode 268 */ getMultiWindowProfile(Context context, WindowBounds windowBounds)269 public DeviceProfile getMultiWindowProfile(Context context, WindowBounds windowBounds) { 270 // We take the minimum sizes of this profile and it's multi-window variant to ensure that 271 // the system decor is always excluded. 272 Point mwSize = new Point(Math.min(availableWidthPx, windowBounds.availableSize.x), 273 Math.min(availableHeightPx, windowBounds.availableSize.y)); 274 275 DeviceProfile profile = toBuilder(context) 276 .setSizeRange(mwSize, mwSize) 277 .setSize(windowBounds.bounds.width(), windowBounds.bounds.height()) 278 .setWindowPosition(windowBounds.bounds.left, windowBounds.bounds.top) 279 .setMultiWindowMode(true) 280 .build(); 281 282 // If there isn't enough vertical cell padding with the labels displayed, hide the labels. 283 float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx 284 - iconDrawablePaddingPx - profile.iconTextSizePx; 285 if (workspaceCellPaddingY < profile.iconDrawablePaddingPx * 2) { 286 profile.adjustToHideWorkspaceLabels(); 287 } 288 289 // We use these scales to measure and layout the widgets using their full invariant profile 290 // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans. 291 float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x; 292 float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y; 293 profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY); 294 profile.updateWorkspacePadding(); 295 296 return profile; 297 } 298 299 /** 300 * Inverse of {@link #getMultiWindowProfile(Context, WindowBounds)} 301 * @return device profile corresponding to the current orientation in non multi-window mode. 302 */ getFullScreenProfile()303 public DeviceProfile getFullScreenProfile() { 304 return isLandscape ? inv.landscapeProfile : inv.portraitProfile; 305 } 306 307 /** 308 * Adjusts the profile so that the labels on the Workspace are hidden. 309 * It is important to call this method after the All Apps variables have been set. 310 */ adjustToHideWorkspaceLabels()311 private void adjustToHideWorkspaceLabels() { 312 iconTextSizePx = 0; 313 iconDrawablePaddingPx = 0; 314 cellHeightPx = iconSizePx; 315 autoResizeAllAppsCells(); 316 } 317 318 /** 319 * Re-computes the all-apps cell size to be independent of workspace 320 */ autoResizeAllAppsCells()321 public void autoResizeAllAppsCells() { 322 int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1); 323 allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx 324 + Utilities.calculateTextHeight(allAppsIconTextSizePx) 325 + topBottomPadding * 2; 326 } 327 updateAvailableDimensions(Resources res)328 private void updateAvailableDimensions(Resources res) { 329 updateIconSize(1f, res); 330 331 // Check to see if the icons fit within the available height. If not, then scale down. 332 float usedHeight = (cellHeightPx * inv.numRows); 333 int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y); 334 if (usedHeight > maxHeight) { 335 float scale = maxHeight / usedHeight; 336 updateIconSize(scale, res); 337 } 338 updateAvailableFolderCellDimensions(res); 339 } 340 341 /** 342 * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx, 343 * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants, 344 * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx. 345 */ updateIconSize(float scale, Resources res)346 private void updateIconSize(float scale, Resources res) { 347 // Workspace 348 final boolean isVerticalLayout = isVerticalBarLayout(); 349 float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize; 350 iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, mInfo.metrics) 351 * scale)); 352 iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, mInfo.metrics) * scale); 353 iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale); 354 355 cellHeightPx = iconSizePx + iconDrawablePaddingPx 356 + Utilities.calculateTextHeight(iconTextSizePx); 357 int cellYPadding = (getCellSize().y - cellHeightPx) / 2; 358 if (iconDrawablePaddingPx > cellYPadding && !isVerticalLayout 359 && !isMultiWindowMode) { 360 // Ensures that the label is closer to its corresponding icon. This is not an issue 361 // with vertical bar layout or multi-window mode since the issue is handled separately 362 // with their calls to {@link #adjustToHideWorkspaceLabels}. 363 cellHeightPx -= (iconDrawablePaddingPx - cellYPadding); 364 iconDrawablePaddingPx = cellYPadding; 365 } 366 cellWidthPx = iconSizePx + iconDrawablePaddingPx; 367 368 // All apps 369 if (allAppsHasDifferentNumColumns()) { 370 allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, mInfo.metrics); 371 allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, mInfo.metrics); 372 allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx; 373 // We use 4 below to ensure labels are closer to their corresponding icon. 374 allAppsCellHeightPx = Math.round(allAppsIconSizePx + allAppsIconTextSizePx 375 + (4 * allAppsIconDrawablePaddingPx)); 376 } else { 377 allAppsIconSizePx = iconSizePx; 378 allAppsIconTextSizePx = iconTextSizePx; 379 allAppsIconDrawablePaddingPx = iconDrawablePaddingPx; 380 allAppsCellHeightPx = getCellSize().y; 381 } 382 allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx; 383 384 if (isVerticalBarLayout()) { 385 // Always hide the Workspace text with vertical bar layout. 386 adjustToHideWorkspaceLabels(); 387 } 388 389 // Hotseat 390 if (isVerticalLayout) { 391 hotseatBarSizePx = iconSizePx + hotseatBarSidePaddingStartPx 392 + hotseatBarSidePaddingEndPx; 393 } 394 hotseatCellHeightPx = iconSizePx; 395 396 if (!isVerticalLayout) { 397 int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx 398 - workspacePageIndicatorHeight - edgeMarginPx; 399 float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace; 400 workspaceSpringLoadShrinkFactor = Math.min( 401 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f, 402 1 - (minRequiredHeight / expectedWorkspaceHeight)); 403 } else { 404 workspaceSpringLoadShrinkFactor = 405 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f; 406 } 407 408 // Folder icon 409 folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx); 410 folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2; 411 } 412 updateAvailableFolderCellDimensions(Resources res)413 private void updateAvailableFolderCellDimensions(Resources res) { 414 int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top) 415 + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom) 416 + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size)); 417 418 updateFolderCellSize(1f, res); 419 420 // Don't let the folder get too close to the edges of the screen. 421 int folderMargin = edgeMarginPx * 2; 422 Point totalWorkspacePadding = getTotalWorkspacePadding(); 423 424 // Check if the icons fit within the available height. 425 float contentUsedHeight = folderCellHeightPx * inv.numFolderRows; 426 int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize 427 - folderMargin; 428 float scaleY = contentMaxHeight / contentUsedHeight; 429 430 // Check if the icons fit within the available width. 431 float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns; 432 int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin; 433 float scaleX = contentMaxWidth / contentUsedWidth; 434 435 float scale = Math.min(scaleX, scaleY); 436 if (scale < 1f) { 437 updateFolderCellSize(scale, res); 438 } 439 } 440 updateFolderCellSize(float scale, Resources res)441 private void updateFolderCellSize(float scale, Resources res) { 442 folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics) * scale); 443 folderChildTextSizePx = 444 (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale); 445 446 int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx); 447 int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale); 448 int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale); 449 450 folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX; 451 folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight; 452 folderChildDrawablePaddingPx = Math.max(0, 453 (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3); 454 } 455 updateInsets(Rect insets)456 public void updateInsets(Rect insets) { 457 mInsets.set(insets); 458 updateWorkspacePadding(); 459 } 460 461 /** 462 * The current device insets. This is generally same as the insets being dispatched to 463 * {@link Insettable} elements, but can differ if the element is using a different profile. 464 */ getInsets()465 public Rect getInsets() { 466 return mInsets; 467 } 468 getCellSize()469 public Point getCellSize() { 470 return getCellSize(inv.numColumns, inv.numRows); 471 } 472 getCellSize(int numColumns, int numRows)473 private Point getCellSize(int numColumns, int numRows) { 474 Point result = new Point(); 475 // Since we are only concerned with the overall padding, layout direction does 476 // not matter. 477 Point padding = getTotalWorkspacePadding(); 478 result.x = calculateCellWidth(availableWidthPx - padding.x 479 - cellLayoutPaddingLeftRightPx * 2, numColumns); 480 result.y = calculateCellHeight(availableHeightPx - padding.y 481 - cellLayoutBottomPaddingPx, numRows); 482 return result; 483 } 484 getTotalWorkspacePadding()485 public Point getTotalWorkspacePadding() { 486 updateWorkspacePadding(); 487 return new Point(workspacePadding.left + workspacePadding.right, 488 workspacePadding.top + workspacePadding.bottom); 489 } 490 491 /** 492 * Updates {@link #workspacePadding} as a result of any internal value change to reflect the 493 * new workspace padding 494 */ updateWorkspacePadding()495 private void updateWorkspacePadding() { 496 Rect padding = workspacePadding; 497 if (isVerticalBarLayout()) { 498 padding.top = 0; 499 padding.bottom = edgeMarginPx; 500 if (isSeascape()) { 501 padding.left = hotseatBarSizePx; 502 padding.right = hotseatBarSidePaddingStartPx; 503 } else { 504 padding.left = hotseatBarSidePaddingStartPx; 505 padding.right = hotseatBarSizePx; 506 } 507 } else { 508 int paddingBottom = hotseatBarSizePx + workspacePageIndicatorHeight 509 - mWorkspacePageIndicatorOverlapWorkspace; 510 if (isTablet) { 511 // Pad the left and right of the workspace to ensure consistent spacing 512 // between all icons 513 // The amount of screen space available for left/right padding. 514 int availablePaddingX = Math.max(0, widthPx - ((inv.numColumns * cellWidthPx) + 515 ((inv.numColumns - 1) * cellWidthPx))); 516 availablePaddingX = (int) Math.min(availablePaddingX, 517 widthPx * MAX_HORIZONTAL_PADDING_PERCENT); 518 int availablePaddingY = Math.max(0, heightPx - edgeMarginPx - paddingBottom 519 - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx 520 - hotseatBarBottomPaddingPx); 521 padding.set(availablePaddingX / 2, edgeMarginPx + availablePaddingY / 2, 522 availablePaddingX / 2, paddingBottom + availablePaddingY / 2); 523 } else { 524 // Pad the top and bottom of the workspace with search/hotseat bar sizes 525 padding.set(desiredWorkspaceLeftRightMarginPx, 526 edgeMarginPx, 527 desiredWorkspaceLeftRightMarginPx, 528 paddingBottom); 529 } 530 } 531 } 532 getHotseatLayoutPadding()533 public Rect getHotseatLayoutPadding() { 534 if (isVerticalBarLayout()) { 535 if (isSeascape()) { 536 mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx, 537 mInsets.top, hotseatBarSidePaddingEndPx, mInsets.bottom); 538 } else { 539 mHotseatPadding.set(hotseatBarSidePaddingEndPx, mInsets.top, 540 mInsets.right + hotseatBarSidePaddingStartPx, mInsets.bottom); 541 } 542 } else { 543 // We want the edges of the hotseat to line up with the edges of the workspace, but the 544 // icons in the hotseat are a different size, and so don't line up perfectly. To account 545 // for this, we pad the left and right of the hotseat with half of the difference of a 546 // workspace cell vs a hotseat cell. 547 float workspaceCellWidth = (float) widthPx / inv.numColumns; 548 float hotseatCellWidth = (float) widthPx / inv.numHotseatIcons; 549 int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2); 550 mHotseatPadding.set( 551 hotseatAdjustment + workspacePadding.left + cellLayoutPaddingLeftRightPx 552 + mInsets.left, 553 hotseatBarTopPaddingPx, 554 hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx 555 + mInsets.right, 556 hotseatBarBottomPaddingPx + mInsets.bottom + cellLayoutBottomPaddingPx); 557 } 558 return mHotseatPadding; 559 } 560 561 /** 562 * @return the bounds for which the open folders should be contained within 563 */ getAbsoluteOpenFolderBounds()564 public Rect getAbsoluteOpenFolderBounds() { 565 if (isVerticalBarLayout()) { 566 // Folders should only appear right of the drop target bar and left of the hotseat 567 return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx, 568 mInsets.top, 569 mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx, 570 mInsets.top + availableHeightPx); 571 } else { 572 // Folders should only appear below the drop target bar and above the hotseat 573 return new Rect(mInsets.left + edgeMarginPx, 574 mInsets.top + dropTargetBarSizePx + edgeMarginPx, 575 mInsets.left + availableWidthPx - edgeMarginPx, 576 mInsets.top + availableHeightPx - hotseatBarSizePx 577 - workspacePageIndicatorHeight - edgeMarginPx); 578 } 579 } 580 calculateCellWidth(int width, int countX)581 public static int calculateCellWidth(int width, int countX) { 582 return width / countX; 583 } calculateCellHeight(int height, int countY)584 public static int calculateCellHeight(int height, int countY) { 585 return height / countY; 586 } 587 588 /** 589 * When {@code true}, the device is in landscape mode and the hotseat is on the right column. 590 * When {@code false}, either device is in portrait mode or the device is in landscape mode and 591 * the hotseat is on the bottom row. 592 */ isVerticalBarLayout()593 public boolean isVerticalBarLayout() { 594 return isLandscape && transposeLayoutWithOrientation; 595 } 596 597 /** 598 * Returns true when the number of workspace columns and all apps columns differs. 599 */ allAppsHasDifferentNumColumns()600 private boolean allAppsHasDifferentNumColumns() { 601 return inv.numAllAppsColumns != inv.numColumns; 602 } 603 604 /** 605 * Updates orientation information and returns true if it has changed from the previous value. 606 */ updateIsSeascape(Context context)607 public boolean updateIsSeascape(Context context) { 608 if (isVerticalBarLayout()) { 609 boolean isSeascape = DefaultDisplay.INSTANCE.get(context).getInfo().rotation 610 == Surface.ROTATION_270; 611 if (mIsSeascape != isSeascape) { 612 mIsSeascape = isSeascape; 613 return true; 614 } 615 } 616 return false; 617 } 618 isSeascape()619 public boolean isSeascape() { 620 return isVerticalBarLayout() && mIsSeascape; 621 } 622 shouldFadeAdjacentWorkspaceScreens()623 public boolean shouldFadeAdjacentWorkspaceScreens() { 624 return isVerticalBarLayout() || isLargeTablet; 625 } 626 getCellHeight(@ontainerType int containerType)627 public int getCellHeight(@ContainerType int containerType) { 628 switch (containerType) { 629 case CellLayout.WORKSPACE: 630 return cellHeightPx; 631 case CellLayout.FOLDER: 632 return folderCellHeightPx; 633 case CellLayout.HOTSEAT: 634 return hotseatCellHeightPx; 635 default: 636 // ?? 637 return 0; 638 } 639 } 640 getContext(Context c, DefaultDisplay.Info info, int orientation)641 private static Context getContext(Context c, DefaultDisplay.Info info, int orientation) { 642 Configuration config = new Configuration(c.getResources().getConfiguration()); 643 config.orientation = orientation; 644 config.densityDpi = info.metrics.densityDpi; 645 return c.createConfigurationContext(config); 646 } 647 648 /** 649 * Callback when a component changes the DeviceProfile associated with it, as a result of 650 * configuration change 651 */ 652 public interface OnDeviceProfileChangeListener { 653 654 /** 655 * Called when the device profile is reassigned. Note that for layout and measurements, it 656 * is sufficient to listen for inset changes. Use this callback when you need to perform 657 * a one time operation. 658 */ onDeviceProfileChanged(DeviceProfile dp)659 void onDeviceProfileChanged(DeviceProfile dp); 660 } 661 662 public static class Builder { 663 private Context mContext; 664 private InvariantDeviceProfile mInv; 665 private DefaultDisplay.Info mInfo; 666 667 private final Point mWindowPosition = new Point(); 668 private Point mMinSize, mMaxSize; 669 private int mWidth, mHeight; 670 671 private boolean mIsLandscape; 672 private boolean mIsMultiWindowMode = false; 673 private boolean mTransposeLayoutWithOrientation; 674 Builder(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info)675 public Builder(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info) { 676 mContext = context; 677 mInv = inv; 678 mInfo = info; 679 mTransposeLayoutWithOrientation = context.getResources() 680 .getBoolean(R.bool.hotseat_transpose_layout_with_orientation); 681 } 682 setSizeRange(Point minSize, Point maxSize)683 public Builder setSizeRange(Point minSize, Point maxSize) { 684 mMinSize = minSize; 685 mMaxSize = maxSize; 686 return this; 687 } 688 setSize(int width, int height)689 public Builder setSize(int width, int height) { 690 mWidth = width; 691 mHeight = height; 692 mIsLandscape = mWidth > mHeight; 693 return this; 694 } 695 setMultiWindowMode(boolean isMultiWindowMode)696 public Builder setMultiWindowMode(boolean isMultiWindowMode) { 697 mIsMultiWindowMode = isMultiWindowMode; 698 return this; 699 } 700 701 /** 702 * Sets the window position if not full-screen 703 */ setWindowPosition(int x, int y)704 public Builder setWindowPosition(int x, int y) { 705 mWindowPosition.set(x, y); 706 return this; 707 } 708 setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation)709 public Builder setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation) { 710 mTransposeLayoutWithOrientation = transposeLayoutWithOrientation; 711 return this; 712 } 713 build()714 public DeviceProfile build() { 715 return new DeviceProfile(mContext, mInv, mInfo, mMinSize, mMaxSize, 716 mWidth, mHeight, mIsLandscape, mIsMultiWindowMode, 717 mTransposeLayoutWithOrientation, mWindowPosition); 718 } 719 } 720 721 } 722