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.appwidget.AppWidgetHostView; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.res.Configuration; 23 import android.content.res.Resources; 24 import android.graphics.Point; 25 import android.graphics.PointF; 26 import android.graphics.Rect; 27 import android.util.DisplayMetrics; 28 import android.view.Gravity; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.ViewGroup.LayoutParams; 32 import android.widget.FrameLayout; 33 34 import com.android.launcher3.CellLayout.ContainerType; 35 import com.android.launcher3.badge.BadgeRenderer; 36 37 import java.util.ArrayList; 38 39 public class DeviceProfile { 40 41 public interface LauncherLayoutChangeListener { 42 void onLauncherLayoutChanged(); 43 } 44 45 public final InvariantDeviceProfile inv; 46 47 // Device properties 48 public final boolean isTablet; 49 public final boolean isLargeTablet; 50 public final boolean isPhone; 51 public final boolean transposeLayoutWithOrientation; 52 53 // Device properties in current orientation 54 public final boolean isLandscape; 55 public final int widthPx; 56 public final int heightPx; 57 public final int availableWidthPx; 58 public final int availableHeightPx; 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 // Overview mode 69 private final int overviewModeMinIconZoneHeightPx; 70 private final int overviewModeMaxIconZoneHeightPx; 71 private final int overviewModeBarItemWidthPx; 72 private final int overviewModeBarSpacerWidthPx; 73 private final float overviewModeIconZoneRatio; 74 75 // Workspace 76 private final int desiredWorkspaceLeftRightMarginPx; 77 public final int cellLayoutPaddingLeftRightPx; 78 public final int cellLayoutBottomPaddingPx; 79 public final int edgeMarginPx; 80 public final Rect defaultWidgetPadding; 81 private final int defaultPageSpacingPx; 82 private final int topWorkspacePadding; 83 public float workspaceSpringLoadShrinkFactor; 84 public final int workspaceSpringLoadedBottomSpace; 85 86 // Page indicator 87 private int pageIndicatorSizePx; 88 private final int pageIndicatorLandLeftNavBarGutterPx; 89 private final int pageIndicatorLandRightNavBarGutterPx; 90 private final int pageIndicatorLandWorkspaceOffsetPx; 91 92 // Workspace icons 93 public int iconSizePx; 94 public int iconTextSizePx; 95 public int iconDrawablePaddingPx; 96 public int iconDrawablePaddingOriginalPx; 97 98 public int cellWidthPx; 99 public int cellHeightPx; 100 public int workspaceCellPaddingXPx; 101 102 // Folder 103 public int folderBackgroundOffset; 104 public int folderIconSizePx; 105 public int folderIconPreviewPadding; 106 107 // Folder cell 108 public int folderCellWidthPx; 109 public int folderCellHeightPx; 110 111 // Folder child 112 public int folderChildIconSizePx; 113 public int folderChildTextSizePx; 114 public int folderChildDrawablePaddingPx; 115 116 // Hotseat 117 public int hotseatCellHeightPx; 118 // In portrait: size = height, in landscape: size = width 119 public int hotseatBarSizePx; 120 public int hotseatBarTopPaddingPx; 121 public int hotseatBarBottomPaddingPx; 122 123 public int hotseatBarLeftNavBarLeftPaddingPx; 124 public int hotseatBarLeftNavBarRightPaddingPx; 125 126 public int hotseatBarRightNavBarLeftPaddingPx; 127 public int hotseatBarRightNavBarRightPaddingPx; 128 129 // All apps 130 public int allAppsCellHeightPx; 131 public int allAppsNumCols; 132 public int allAppsNumPredictiveCols; 133 public int allAppsButtonVisualSize; 134 public int allAppsIconSizePx; 135 public int allAppsIconDrawablePaddingPx; 136 public float allAppsIconTextSizePx; 137 138 // Widgets 139 public final PointF appWidgetScale = new PointF(1.0f, 1.0f); 140 141 // Drop Target 142 public int dropTargetBarSizePx; 143 144 // Insets 145 private Rect mInsets = new Rect(); 146 147 // Listeners 148 private ArrayList<LauncherLayoutChangeListener> mListeners = new ArrayList<>(); 149 150 // Icon badges 151 public BadgeRenderer mBadgeRenderer; 152 153 public DeviceProfile(Context context, InvariantDeviceProfile inv, 154 Point minSize, Point maxSize, 155 int width, int height, boolean isLandscape) { 156 157 this.inv = inv; 158 this.isLandscape = isLandscape; 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 168 // Some more constants 169 transposeLayoutWithOrientation = 170 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation); 171 172 context = getContext(context, isVerticalBarLayout() 173 ? Configuration.ORIENTATION_LANDSCAPE 174 : Configuration.ORIENTATION_PORTRAIT); 175 res = context.getResources(); 176 177 178 ComponentName cn = new ComponentName(context.getPackageName(), 179 this.getClass().getName()); 180 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null); 181 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin); 182 desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx; 183 cellLayoutPaddingLeftRightPx = 184 res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding); 185 cellLayoutBottomPaddingPx = 186 res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_bottom_padding); 187 pageIndicatorSizePx = res.getDimensionPixelSize( 188 R.dimen.dynamic_grid_min_page_indicator_size); 189 pageIndicatorLandLeftNavBarGutterPx = res.getDimensionPixelSize( 190 R.dimen.dynamic_grid_page_indicator_land_left_nav_bar_gutter_width); 191 pageIndicatorLandRightNavBarGutterPx = res.getDimensionPixelSize( 192 R.dimen.dynamic_grid_page_indicator_land_right_nav_bar_gutter_width); 193 pageIndicatorLandWorkspaceOffsetPx = 194 res.getDimensionPixelSize(R.dimen.all_apps_caret_workspace_offset); 195 defaultPageSpacingPx = 196 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing); 197 topWorkspacePadding = 198 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_top_padding); 199 overviewModeMinIconZoneHeightPx = 200 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height); 201 overviewModeMaxIconZoneHeightPx = 202 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height); 203 overviewModeBarItemWidthPx = 204 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width); 205 overviewModeBarSpacerWidthPx = 206 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width); 207 overviewModeIconZoneRatio = 208 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f; 209 iconDrawablePaddingOriginalPx = 210 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding); 211 dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size); 212 workspaceSpringLoadedBottomSpace = 213 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space); 214 215 workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x); 216 217 hotseatBarTopPaddingPx = 218 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding); 219 hotseatBarBottomPaddingPx = 220 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding); 221 hotseatBarLeftNavBarRightPaddingPx = res.getDimensionPixelSize( 222 R.dimen.dynamic_grid_hotseat_land_left_nav_bar_right_padding); 223 hotseatBarRightNavBarRightPaddingPx = res.getDimensionPixelSize( 224 R.dimen.dynamic_grid_hotseat_land_right_nav_bar_right_padding); 225 hotseatBarLeftNavBarLeftPaddingPx = res.getDimensionPixelSize( 226 R.dimen.dynamic_grid_hotseat_land_left_nav_bar_left_padding); 227 hotseatBarRightNavBarLeftPaddingPx = res.getDimensionPixelSize( 228 R.dimen.dynamic_grid_hotseat_land_right_nav_bar_left_padding); 229 hotseatBarSizePx = isVerticalBarLayout() 230 ? Utilities.pxFromDp(inv.iconSize, dm) 231 : res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_size) 232 + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx; 233 234 // Determine sizes. 235 widthPx = width; 236 heightPx = height; 237 if (isLandscape) { 238 availableWidthPx = maxSize.x; 239 availableHeightPx = minSize.y; 240 } else { 241 availableWidthPx = minSize.x; 242 availableHeightPx = maxSize.y; 243 } 244 245 // Calculate all of the remaining variables. 246 updateAvailableDimensions(dm, res); 247 248 // Now that we have all of the variables calculated, we can tune certain sizes. 249 float aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx); 250 boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0; 251 if (!isVerticalBarLayout() && isPhone && isTallDevice) { 252 // We increase the hotseat size when there is extra space. 253 // ie. For a display with a large aspect ratio, we can keep the icons on the workspace 254 // in portrait mode closer together by adding more height to the hotseat. 255 // Note: This calculation was created after noticing a pattern in the design spec. 256 int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx; 257 hotseatBarSizePx += extraSpace - pageIndicatorSizePx; 258 259 // Recalculate the available dimensions using the new hotseat size. 260 updateAvailableDimensions(dm, res); 261 } 262 263 computeAllAppsButtonSize(context); 264 265 // This is done last, after iconSizePx is calculated above. 266 mBadgeRenderer = new BadgeRenderer(context, iconSizePx); 267 } 268 269 DeviceProfile getMultiWindowProfile(Context context, Point mwSize) { 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 mwSize.set(Math.min(availableWidthPx, mwSize.x), Math.min(availableHeightPx, mwSize.y)); 273 274 // In multi-window mode, we can have widthPx = availableWidthPx 275 // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles' 276 // widthPx and heightPx values where it's needed. 277 DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y, 278 isLandscape); 279 280 // Hide labels on the workspace. 281 profile.adjustToHideWorkspaceLabels(); 282 283 // We use these scales to measure and layout the widgets using their full invariant profile 284 // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans. 285 float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x; 286 float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y; 287 profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY); 288 289 return profile; 290 } 291 292 public void addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) { 293 if (!mListeners.contains(listener)) { 294 mListeners.add(listener); 295 } 296 } 297 298 public void removeLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) { 299 if (mListeners.contains(listener)) { 300 mListeners.remove(listener); 301 } 302 } 303 304 /** 305 * Adjusts the profile so that the labels on the Workspace are hidden. 306 * It is important to call this method after the All Apps variables have been set. 307 */ 308 private void adjustToHideWorkspaceLabels() { 309 iconTextSizePx = 0; 310 iconDrawablePaddingPx = 0; 311 cellHeightPx = iconSizePx; 312 313 // In normal cases, All Apps cell height should equal the Workspace cell height. 314 // Since we are removing labels from the Workspace, we need to manually compute the 315 // All Apps cell height. 316 int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1); 317 allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx 318 + Utilities.calculateTextHeight(allAppsIconTextSizePx) 319 + topBottomPadding * 2; 320 } 321 322 /** 323 * Determine the exact visual footprint of the all apps button, taking into account scaling 324 * and internal padding of the drawable. 325 */ 326 private void computeAllAppsButtonSize(Context context) { 327 Resources res = context.getResources(); 328 float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f; 329 allAppsButtonVisualSize = (int) (iconSizePx * (1 - padding)) - context.getResources() 330 .getDimensionPixelSize(R.dimen.all_apps_button_scale_down); 331 } 332 333 private void updateAvailableDimensions(DisplayMetrics dm, Resources res) { 334 updateIconSize(1f, res, dm); 335 336 // Check to see if the icons fit within the available height. If not, then scale down. 337 float usedHeight = (cellHeightPx * inv.numRows); 338 int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y); 339 if (usedHeight > maxHeight) { 340 float scale = maxHeight / usedHeight; 341 updateIconSize(scale, res, dm); 342 } 343 updateAvailableFolderCellDimensions(dm, res); 344 } 345 346 private void updateIconSize(float scale, Resources res, DisplayMetrics dm) { 347 // Workspace 348 float invIconSizePx = isVerticalBarLayout() ? inv.landscapeIconSize : inv.iconSize; 349 iconSizePx = (int) (Utilities.pxFromDp(invIconSizePx, dm) * scale); 350 iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale); 351 iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale); 352 353 cellHeightPx = iconSizePx + iconDrawablePaddingPx 354 + Utilities.calculateTextHeight(iconTextSizePx); 355 int cellYPadding = (getCellSize().y - cellHeightPx) / 2; 356 if (iconDrawablePaddingPx > cellYPadding && !isVerticalBarLayout() 357 && !inMultiWindowMode()) { 358 // Ensures that the label is closer to its corresponding icon. This is not an issue 359 // with vertical bar layout or multi-window mode since the issue is handled separately 360 // with their calls to {@link #adjustToHideWorkspaceLabels}. 361 cellHeightPx -= (iconDrawablePaddingPx - cellYPadding); 362 iconDrawablePaddingPx = cellYPadding; 363 } 364 cellWidthPx = iconSizePx + iconDrawablePaddingPx; 365 366 // All apps 367 allAppsIconTextSizePx = iconTextSizePx; 368 allAppsIconSizePx = iconSizePx; 369 allAppsIconDrawablePaddingPx = iconDrawablePaddingPx; 370 allAppsCellHeightPx = getCellSize().y; 371 372 if (isVerticalBarLayout()) { 373 // Always hide the Workspace text with vertical bar layout. 374 adjustToHideWorkspaceLabels(); 375 } 376 377 // Hotseat 378 if (isVerticalBarLayout()) { 379 hotseatBarSizePx = iconSizePx; 380 } 381 hotseatCellHeightPx = iconSizePx; 382 383 if (!isVerticalBarLayout()) { 384 int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx 385 - pageIndicatorSizePx - topWorkspacePadding; 386 float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace; 387 workspaceSpringLoadShrinkFactor = Math.min( 388 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f, 389 1 - (minRequiredHeight / expectedWorkspaceHeight)); 390 } else { 391 workspaceSpringLoadShrinkFactor = 392 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f; 393 } 394 395 // Folder icon 396 folderBackgroundOffset = -iconDrawablePaddingPx; 397 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset; 398 folderIconPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); 399 } 400 401 private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) { 402 int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top) 403 + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom) 404 + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size)); 405 406 updateFolderCellSize(1f, dm, res); 407 408 // Don't let the folder get too close to the edges of the screen. 409 int folderMargin = edgeMarginPx; 410 411 // Check if the icons fit within the available height. 412 float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize; 413 int maxHeight = availableHeightPx - getTotalWorkspacePadding().y - folderMargin; 414 float scaleY = maxHeight / usedHeight; 415 416 // Check if the icons fit within the available width. 417 float usedWidth = folderCellWidthPx * inv.numFolderColumns; 418 int maxWidth = availableWidthPx - getTotalWorkspacePadding().x - folderMargin; 419 float scaleX = maxWidth / usedWidth; 420 421 float scale = Math.min(scaleX, scaleY); 422 if (scale < 1f) { 423 updateFolderCellSize(scale, dm, res); 424 } 425 } 426 427 private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) { 428 folderChildIconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale); 429 folderChildTextSizePx = 430 (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale); 431 432 int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx); 433 int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale); 434 int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale); 435 436 folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX; 437 folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight; 438 folderChildDrawablePaddingPx = Math.max(0, 439 (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3); 440 } 441 442 public void updateInsets(Rect insets) { 443 mInsets.set(insets); 444 } 445 446 public void updateAppsViewNumCols() { 447 allAppsNumCols = allAppsNumPredictiveCols = inv.numColumns; 448 } 449 450 /** Returns the width and height of the search bar, ignoring any padding. */ 451 public Point getSearchBarDimensForWidgetOpts() { 452 if (isVerticalBarLayout()) { 453 return new Point(dropTargetBarSizePx, availableHeightPx - 2 * edgeMarginPx); 454 } else { 455 int gap; 456 if (isTablet) { 457 // Pad the left and right of the workspace to ensure consistent spacing 458 // between all icons 459 int width = getCurrentWidth(); 460 // XXX: If the icon size changes across orientations, we will have to take 461 // that into account here too. 462 gap = ((width - 2 * edgeMarginPx 463 - (inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1))) 464 + edgeMarginPx; 465 } else { 466 gap = desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right; 467 } 468 return new Point(availableWidthPx - 2 * gap, dropTargetBarSizePx); 469 } 470 } 471 472 public Point getCellSize() { 473 Point result = new Point(); 474 // Since we are only concerned with the overall padding, layout direction does 475 // not matter. 476 Point padding = getTotalWorkspacePadding(); 477 result.x = calculateCellWidth(availableWidthPx - padding.x 478 - cellLayoutPaddingLeftRightPx * 2, inv.numColumns); 479 result.y = calculateCellHeight(availableHeightPx - padding.y 480 - cellLayoutBottomPaddingPx, inv.numRows); 481 return result; 482 } 483 484 public Point getTotalWorkspacePadding() { 485 Rect padding = getWorkspacePadding(null); 486 return new Point(padding.left + padding.right, padding.top + padding.bottom); 487 } 488 489 /** 490 * Returns the workspace padding in the specified orientation. 491 */ 492 public Rect getWorkspacePadding(Rect recycle) { 493 Rect padding = recycle == null ? new Rect() : recycle; 494 if (isVerticalBarLayout()) { 495 if (mInsets.left > 0) { 496 padding.set(mInsets.left + pageIndicatorLandLeftNavBarGutterPx, 497 0, 498 hotseatBarSizePx + hotseatBarLeftNavBarRightPaddingPx 499 + hotseatBarLeftNavBarLeftPaddingPx 500 - mInsets.left, 501 edgeMarginPx); 502 } else { 503 padding.set(pageIndicatorLandRightNavBarGutterPx, 504 0, 505 hotseatBarSizePx + hotseatBarRightNavBarRightPaddingPx 506 + hotseatBarRightNavBarLeftPaddingPx, 507 edgeMarginPx); 508 } 509 } else { 510 int paddingBottom = hotseatBarSizePx + pageIndicatorSizePx; 511 if (isTablet) { 512 // Pad the left and right of the workspace to ensure consistent spacing 513 // between all icons 514 int width = getCurrentWidth(); 515 int height = getCurrentHeight(); 516 // The amount of screen space available for left/right padding. 517 int availablePaddingX = Math.max(0, width - ((inv.numColumns * cellWidthPx) + 518 ((inv.numColumns - 1) * cellWidthPx))); 519 availablePaddingX = (int) Math.min(availablePaddingX, 520 width * MAX_HORIZONTAL_PADDING_PERCENT); 521 int availablePaddingY = Math.max(0, height - topWorkspacePadding - paddingBottom 522 - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx 523 - hotseatBarBottomPaddingPx); 524 padding.set(availablePaddingX / 2, topWorkspacePadding + availablePaddingY / 2, 525 availablePaddingX / 2, paddingBottom + availablePaddingY / 2); 526 } else { 527 // Pad the top and bottom of the workspace with search/hotseat bar sizes 528 padding.set(desiredWorkspaceLeftRightMarginPx, 529 topWorkspacePadding, 530 desiredWorkspaceLeftRightMarginPx, 531 paddingBottom); 532 } 533 } 534 return padding; 535 } 536 537 /** 538 * @return the bounds for which the open folders should be contained within 539 */ 540 public Rect getAbsoluteOpenFolderBounds() { 541 if (isVerticalBarLayout()) { 542 // Folders should only appear right of the drop target bar and left of the hotseat 543 return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx, 544 mInsets.top, 545 mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx, 546 mInsets.top + availableHeightPx); 547 } else { 548 // Folders should only appear below the drop target bar and above the hotseat 549 return new Rect(mInsets.left, 550 mInsets.top + dropTargetBarSizePx + edgeMarginPx, 551 mInsets.left + availableWidthPx, 552 mInsets.top + availableHeightPx - hotseatBarSizePx 553 - pageIndicatorSizePx - edgeMarginPx); 554 } 555 } 556 557 private int getWorkspacePageSpacing() { 558 if (isVerticalBarLayout() || isLargeTablet) { 559 // In landscape mode the page spacing is set to the default. 560 return defaultPageSpacingPx; 561 } else { 562 // In portrait, we want the pages spaced such that there is no 563 // overhang of the previous / next page into the current page viewport. 564 // We assume symmetrical padding in portrait mode. 565 return Math.max(defaultPageSpacingPx, getWorkspacePadding(null).left + 1); 566 } 567 } 568 569 int getOverviewModeButtonBarHeight() { 570 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx); 571 return Utilities.boundToRange(zoneHeight, 572 overviewModeMinIconZoneHeightPx, 573 overviewModeMaxIconZoneHeightPx); 574 } 575 576 public static int calculateCellWidth(int width, int countX) { 577 return width / countX; 578 } 579 public static int calculateCellHeight(int height, int countY) { 580 return height / countY; 581 } 582 583 /** 584 * When {@code true}, the device is in landscape mode and the hotseat is on the right column. 585 * When {@code false}, either device is in portrait mode or the device is in landscape mode and 586 * the hotseat is on the bottom row. 587 */ 588 public boolean isVerticalBarLayout() { 589 return isLandscape && transposeLayoutWithOrientation; 590 } 591 592 boolean shouldFadeAdjacentWorkspaceScreens() { 593 return isVerticalBarLayout() || isLargeTablet; 594 } 595 596 private int getVisibleChildCount(ViewGroup parent) { 597 int visibleChildren = 0; 598 for (int i = 0; i < parent.getChildCount(); i++) { 599 if (parent.getChildAt(i).getVisibility() != View.GONE) { 600 visibleChildren++; 601 } 602 } 603 return visibleChildren; 604 } 605 606 public void layout(Launcher launcher, boolean notifyListeners) { 607 FrameLayout.LayoutParams lp; 608 boolean hasVerticalBarLayout = isVerticalBarLayout(); 609 610 // Layout the search bar space 611 Point searchBarBounds = getSearchBarDimensForWidgetOpts(); 612 View searchBar = launcher.getDropTargetBar(); 613 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams(); 614 lp.width = searchBarBounds.x; 615 lp.height = searchBarBounds.y; 616 lp.topMargin = mInsets.top + edgeMarginPx; 617 searchBar.setLayoutParams(lp); 618 619 // Layout the workspace 620 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace); 621 Rect workspacePadding = getWorkspacePadding(null); 622 workspace.setPadding(workspacePadding.left, workspacePadding.top, workspacePadding.right, 623 workspacePadding.bottom); 624 workspace.setPageSpacing(getWorkspacePageSpacing()); 625 626 // Layout the hotseat 627 Hotseat hotseat = (Hotseat) launcher.findViewById(R.id.hotseat); 628 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams(); 629 // We want the edges of the hotseat to line up with the edges of the workspace, but the 630 // icons in the hotseat are a different size, and so don't line up perfectly. To account for 631 // this, we pad the left and right of the hotseat with half of the difference of a workspace 632 // cell vs a hotseat cell. 633 float workspaceCellWidth = (float) getCurrentWidth() / inv.numColumns; 634 float hotseatCellWidth = (float) getCurrentWidth() / inv.numHotseatIcons; 635 int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2); 636 if (hasVerticalBarLayout) { 637 // Vertical hotseat -- The hotseat is fixed in the layout to be on the right of the 638 // screen regardless of RTL 639 int paddingRight = mInsets.left > 0 640 ? hotseatBarLeftNavBarRightPaddingPx 641 : hotseatBarRightNavBarRightPaddingPx; 642 int paddingLeft = mInsets.left > 0 643 ? hotseatBarLeftNavBarLeftPaddingPx 644 : hotseatBarRightNavBarLeftPaddingPx; 645 646 lp.gravity = Gravity.RIGHT; 647 lp.width = hotseatBarSizePx + mInsets.left + mInsets.right 648 + paddingLeft + paddingRight; 649 lp.height = LayoutParams.MATCH_PARENT; 650 651 hotseat.getLayout().setPadding(mInsets.left + cellLayoutPaddingLeftRightPx 652 + paddingLeft, 653 mInsets.top, 654 mInsets.right + cellLayoutPaddingLeftRightPx + paddingRight, 655 workspacePadding.bottom + cellLayoutBottomPaddingPx); 656 } else if (isTablet) { 657 // Pad the hotseat with the workspace padding calculated above 658 lp.gravity = Gravity.BOTTOM; 659 lp.width = LayoutParams.MATCH_PARENT; 660 lp.height = hotseatBarSizePx + mInsets.bottom; 661 hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left 662 + cellLayoutPaddingLeftRightPx, 663 hotseatBarTopPaddingPx, 664 hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx, 665 hotseatBarBottomPaddingPx + mInsets.bottom + cellLayoutBottomPaddingPx); 666 } else { 667 // For phones, layout the hotseat without any bottom margin 668 // to ensure that we have space for the folders 669 lp.gravity = Gravity.BOTTOM; 670 lp.width = LayoutParams.MATCH_PARENT; 671 lp.height = hotseatBarSizePx + mInsets.bottom; 672 hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left 673 + cellLayoutPaddingLeftRightPx, 674 hotseatBarTopPaddingPx, 675 hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx, 676 hotseatBarBottomPaddingPx + mInsets.bottom + cellLayoutBottomPaddingPx); 677 } 678 hotseat.setLayoutParams(lp); 679 680 // Layout the page indicators 681 View pageIndicator = launcher.findViewById(R.id.page_indicator); 682 if (pageIndicator != null) { 683 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams(); 684 if (isVerticalBarLayout()) { 685 if (mInsets.left > 0) { 686 lp.leftMargin = mInsets.left; 687 } else { 688 lp.leftMargin = pageIndicatorLandWorkspaceOffsetPx; 689 } 690 lp.bottomMargin = workspacePadding.bottom; 691 } else { 692 // Put the page indicators above the hotseat 693 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; 694 lp.height = pageIndicatorSizePx; 695 lp.bottomMargin = hotseatBarSizePx + mInsets.bottom; 696 } 697 pageIndicator.setLayoutParams(lp); 698 } 699 700 // Layout the Overview Mode 701 ViewGroup overviewMode = launcher.getOverviewPanel(); 702 if (overviewMode != null) { 703 int visibleChildCount = getVisibleChildCount(overviewMode); 704 int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx; 705 int maxWidth = totalItemWidth + (visibleChildCount - 1) * overviewModeBarSpacerWidthPx; 706 707 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams(); 708 lp.width = Math.min(availableWidthPx, maxWidth); 709 lp.height = getOverviewModeButtonBarHeight(); 710 lp.bottomMargin = mInsets.bottom; 711 overviewMode.setLayoutParams(lp); 712 } 713 714 // Layout the AllAppsRecyclerView 715 View view = launcher.findViewById(R.id.apps_list_view); 716 int paddingLeftRight = desiredWorkspaceLeftRightMarginPx + cellLayoutPaddingLeftRightPx; 717 view.setPadding(paddingLeftRight, view.getPaddingTop(), paddingLeftRight, 718 view.getPaddingBottom()); 719 720 if (notifyListeners) { 721 for (int i = mListeners.size() - 1; i >= 0; i--) { 722 mListeners.get(i).onLauncherLayoutChanged(); 723 } 724 } 725 } 726 727 private int getCurrentWidth() { 728 return isLandscape 729 ? Math.max(widthPx, heightPx) 730 : Math.min(widthPx, heightPx); 731 } 732 733 private int getCurrentHeight() { 734 return isLandscape 735 ? Math.min(widthPx, heightPx) 736 : Math.max(widthPx, heightPx); 737 } 738 739 public int getCellHeight(@ContainerType int containerType) { 740 switch (containerType) { 741 case CellLayout.WORKSPACE: 742 return cellHeightPx; 743 case CellLayout.FOLDER: 744 return folderCellHeightPx; 745 case CellLayout.HOTSEAT: 746 return hotseatCellHeightPx; 747 default: 748 // ?? 749 return 0; 750 } 751 } 752 753 /** 754 * @return the left/right paddings for all containers. 755 */ 756 public final int[] getContainerPadding() { 757 // No paddings for portrait phone 758 if (isPhone && !isVerticalBarLayout()) { 759 return new int[] {0, 0}; 760 } 761 762 // In landscape, we match the width of the workspace 763 Rect padding = getWorkspacePadding(null); 764 return new int[] { padding.left - mInsets.left, padding.right + mInsets.left}; 765 } 766 767 public boolean inMultiWindowMode() { 768 return this != inv.landscapeProfile && this != inv.portraitProfile; 769 } 770 771 public boolean shouldIgnoreLongPressToOverview(float touchX) { 772 boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeMarginPx; 773 boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeMarginPx); 774 return !inMultiWindowMode() && (touchedLhsEdge || touchedRhsEdge); 775 } 776 777 private static Context getContext(Context c, int orientation) { 778 Configuration context = new Configuration(c.getResources().getConfiguration()); 779 context.orientation = orientation; 780 return c.createConfigurationContext(context); 781 782 } 783 } 784