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