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.Resources; 23 import android.graphics.Point; 24 import android.graphics.PointF; 25 import android.graphics.Rect; 26 import android.util.DisplayMetrics; 27 import android.view.Gravity; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.view.ViewGroup.LayoutParams; 31 import android.widget.FrameLayout; 32 33 import com.android.launcher3.CellLayout.ContainerType; 34 import com.android.launcher3.badge.BadgeRenderer; 35 import com.android.launcher3.config.FeatureFlags; 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 // Overview mode 67 private final int overviewModeMinIconZoneHeightPx; 68 private final int overviewModeMaxIconZoneHeightPx; 69 private final int overviewModeBarItemWidthPx; 70 private final int overviewModeBarSpacerWidthPx; 71 private final float overviewModeIconZoneRatio; 72 73 // Workspace 74 private int desiredWorkspaceLeftRightMarginPx; 75 public final int edgeMarginPx; 76 public final Rect defaultWidgetPadding; 77 private final int defaultPageSpacingPx; 78 private final int topWorkspacePadding; 79 public float workspaceSpringLoadShrinkFactor; 80 public final int workspaceSpringLoadedBottomSpace; 81 82 // Page indicator 83 private final int pageIndicatorHeightPx; 84 private final int pageIndicatorLandGutterLeftNavBarPx; 85 private final int pageIndicatorLandGutterRightNavBarPx; 86 private final int pageIndicatorLandWorkspaceOffsetPx; 87 88 // Workspace icons 89 public int iconSizePx; 90 public int iconTextSizePx; 91 public int iconDrawablePaddingPx; 92 public int iconDrawablePaddingOriginalPx; 93 94 public int cellWidthPx; 95 public int cellHeightPx; 96 97 // Folder 98 public int folderBackgroundOffset; 99 public int folderIconSizePx; 100 public int folderIconPreviewPadding; 101 102 // Folder cell 103 public int folderCellWidthPx; 104 public int folderCellHeightPx; 105 106 // Folder child 107 public int folderChildIconSizePx; 108 public int folderChildTextSizePx; 109 public int folderChildDrawablePaddingPx; 110 111 // Hotseat 112 public int hotseatCellWidthPx; 113 public int hotseatCellHeightPx; 114 public int hotseatIconSizePx; 115 public int hotseatBarHeightPx; 116 private int hotseatBarTopPaddingPx; 117 private int hotseatBarBottomPaddingPx; 118 private int hotseatLandGutterPx; 119 120 // All apps 121 public int allAppsNumCols; 122 public int allAppsNumPredictiveCols; 123 public int allAppsButtonVisualSize; 124 public int allAppsIconSizePx; 125 public int allAppsIconDrawablePaddingPx; 126 public float allAppsIconTextSizePx; 127 128 // Widgets 129 public final PointF appWidgetScale = new PointF(1.0f, 1.0f); 130 131 // Drop Target 132 public int dropTargetBarSizePx; 133 134 // Insets 135 private Rect mInsets = new Rect(); 136 137 // Listeners 138 private ArrayList<LauncherLayoutChangeListener> mListeners = new ArrayList<>(); 139 140 // Icon badges 141 public BadgeRenderer mBadgeRenderer; 142 143 public DeviceProfile(Context context, InvariantDeviceProfile inv, 144 Point minSize, Point maxSize, 145 int width, int height, boolean isLandscape) { 146 147 this.inv = inv; 148 this.isLandscape = isLandscape; 149 150 Resources res = context.getResources(); 151 DisplayMetrics dm = res.getDisplayMetrics(); 152 153 // Constants from resources 154 isTablet = res.getBoolean(R.bool.is_tablet); 155 isLargeTablet = res.getBoolean(R.bool.is_large_tablet); 156 isPhone = !isTablet && !isLargeTablet; 157 158 // Some more constants 159 transposeLayoutWithOrientation = 160 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation); 161 162 ComponentName cn = new ComponentName(context.getPackageName(), 163 this.getClass().getName()); 164 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null); 165 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin); 166 desiredWorkspaceLeftRightMarginPx = edgeMarginPx; 167 pageIndicatorHeightPx = 168 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height); 169 pageIndicatorLandGutterLeftNavBarPx = res.getDimensionPixelSize( 170 R.dimen.dynamic_grid_page_indicator_gutter_width_left_nav_bar); 171 pageIndicatorLandWorkspaceOffsetPx = 172 res.getDimensionPixelSize(R.dimen.all_apps_caret_workspace_offset); 173 pageIndicatorLandGutterRightNavBarPx = res.getDimensionPixelSize( 174 R.dimen.dynamic_grid_page_indicator_gutter_width_right_nav_bar); 175 defaultPageSpacingPx = 176 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing); 177 topWorkspacePadding = 178 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_top_padding); 179 overviewModeMinIconZoneHeightPx = 180 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height); 181 overviewModeMaxIconZoneHeightPx = 182 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height); 183 overviewModeBarItemWidthPx = 184 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width); 185 overviewModeBarSpacerWidthPx = 186 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width); 187 overviewModeIconZoneRatio = 188 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f; 189 iconDrawablePaddingOriginalPx = 190 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding); 191 dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size); 192 workspaceSpringLoadedBottomSpace = 193 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space); 194 hotseatBarHeightPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_height); 195 hotseatBarTopPaddingPx = 196 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding); 197 hotseatBarBottomPaddingPx = 0; 198 hotseatLandGutterPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_gutter_width); 199 200 // Determine sizes. 201 widthPx = width; 202 heightPx = height; 203 if (isLandscape) { 204 availableWidthPx = maxSize.x; 205 availableHeightPx = minSize.y; 206 } else { 207 availableWidthPx = minSize.x; 208 availableHeightPx = maxSize.y; 209 } 210 211 // Calculate the remaining vars 212 updateAvailableDimensions(dm, res); 213 computeAllAppsButtonSize(context); 214 215 // This is done last, after iconSizePx is calculated above. 216 mBadgeRenderer = new BadgeRenderer(context, iconSizePx); 217 } 218 219 DeviceProfile getMultiWindowProfile(Context context, Point mwSize) { 220 // In multi-window mode, we can have widthPx = availableWidthPx 221 // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles' 222 // widthPx and heightPx values where it's needed. 223 DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y, 224 isLandscape); 225 226 // Hide labels on the workspace. 227 profile.iconTextSizePx = 0; 228 profile.cellHeightPx = profile.iconSizePx + profile.iconDrawablePaddingPx 229 + Utilities.calculateTextHeight(profile.iconTextSizePx); 230 231 // The nav bar is black so we add bottom padding to visually center hotseat icons. 232 profile.hotseatBarBottomPaddingPx = profile.hotseatBarTopPaddingPx; 233 234 // We use these scales to measure and layout the widgets using their full invariant profile 235 // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans. 236 float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x; 237 float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y; 238 profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY); 239 240 return profile; 241 } 242 243 public void addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) { 244 if (!mListeners.contains(listener)) { 245 mListeners.add(listener); 246 } 247 } 248 249 public void removeLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) { 250 if (mListeners.contains(listener)) { 251 mListeners.remove(listener); 252 } 253 } 254 255 /** 256 * Determine the exact visual footprint of the all apps button, taking into account scaling 257 * and internal padding of the drawable. 258 */ 259 private void computeAllAppsButtonSize(Context context) { 260 Resources res = context.getResources(); 261 float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f; 262 allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding)) - context.getResources() 263 .getDimensionPixelSize(R.dimen.all_apps_button_scale_down); 264 } 265 266 private void updateAvailableDimensions(DisplayMetrics dm, Resources res) { 267 updateIconSize(1f, iconDrawablePaddingOriginalPx, res, dm); 268 269 // Check to see if the icons fit within the available height. If not, then scale down. 270 float usedHeight = (cellHeightPx * inv.numRows); 271 int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y); 272 if (usedHeight > maxHeight) { 273 float scale = maxHeight / usedHeight; 274 updateIconSize(scale, 0, res, dm); 275 } 276 277 updateAvailableFolderCellDimensions(dm, res); 278 } 279 280 private void updateIconSize(float scale, int drawablePadding, Resources res, 281 DisplayMetrics dm) { 282 iconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale); 283 iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale); 284 iconDrawablePaddingPx = drawablePadding; 285 hotseatIconSizePx = (int) (Utilities.pxFromDp(inv.hotseatIconSize, dm) * scale); 286 allAppsIconSizePx = iconSizePx; 287 allAppsIconDrawablePaddingPx = iconDrawablePaddingPx; 288 allAppsIconTextSizePx = iconTextSizePx; 289 290 cellWidthPx = iconSizePx; 291 cellHeightPx = iconSizePx + iconDrawablePaddingPx 292 + Utilities.calculateTextHeight(iconTextSizePx); 293 294 // Hotseat 295 hotseatCellWidthPx = iconSizePx; 296 hotseatCellHeightPx = iconSizePx; 297 298 if (!isVerticalBarLayout()) { 299 int expectedWorkspaceHeight = availableHeightPx - hotseatBarHeightPx 300 - pageIndicatorHeightPx - topWorkspacePadding; 301 float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace; 302 workspaceSpringLoadShrinkFactor = Math.min( 303 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f, 304 1 - (minRequiredHeight / expectedWorkspaceHeight)); 305 } else { 306 workspaceSpringLoadShrinkFactor = 307 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f; 308 } 309 310 // Folder icon 311 folderBackgroundOffset = -edgeMarginPx; 312 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset; 313 folderIconPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); 314 } 315 316 private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) { 317 int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top) 318 + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom) 319 + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size)); 320 321 updateFolderCellSize(1f, dm, res); 322 323 // Don't let the folder get too close to the edges of the screen. 324 int folderMargin = 4 * edgeMarginPx; 325 326 // Check if the icons fit within the available height. 327 float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize; 328 int maxHeight = availableHeightPx - getTotalWorkspacePadding().y - folderMargin; 329 float scaleY = maxHeight / usedHeight; 330 331 // Check if the icons fit within the available width. 332 float usedWidth = folderCellWidthPx * inv.numFolderColumns; 333 int maxWidth = availableWidthPx - getTotalWorkspacePadding().x - folderMargin; 334 float scaleX = maxWidth / usedWidth; 335 336 float scale = Math.min(scaleX, scaleY); 337 if (scale < 1f) { 338 updateFolderCellSize(scale, dm, res); 339 } 340 } 341 342 private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) { 343 folderChildIconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale); 344 folderChildTextSizePx = 345 (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale); 346 347 int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx); 348 int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale); 349 int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale); 350 351 folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX; 352 folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight; 353 folderChildDrawablePaddingPx = Math.max(0, 354 (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3); 355 } 356 357 public void updateInsets(Rect insets) { 358 mInsets.set(insets); 359 } 360 361 public void updateAppsViewNumCols() { 362 allAppsNumCols = allAppsNumPredictiveCols = inv.numColumns; 363 } 364 365 /** Returns the width and height of the search bar, ignoring any padding. */ 366 public Point getSearchBarDimensForWidgetOpts() { 367 if (isVerticalBarLayout()) { 368 return new Point(dropTargetBarSizePx, availableHeightPx - 2 * edgeMarginPx); 369 } else { 370 int gap; 371 if (isTablet) { 372 // Pad the left and right of the workspace to ensure consistent spacing 373 // between all icons 374 int width = getCurrentWidth(); 375 // XXX: If the icon size changes across orientations, we will have to take 376 // that into account here too. 377 gap = ((width - 2 * edgeMarginPx 378 - (inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1))) 379 + edgeMarginPx; 380 } else { 381 gap = desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right; 382 } 383 return new Point(availableWidthPx - 2 * gap, dropTargetBarSizePx); 384 } 385 } 386 387 public Point getCellSize() { 388 Point result = new Point(); 389 // Since we are only concerned with the overall padding, layout direction does 390 // not matter. 391 Point padding = getTotalWorkspacePadding(); 392 result.x = calculateCellWidth(availableWidthPx - padding.x, inv.numColumns); 393 result.y = calculateCellHeight(availableHeightPx - padding.y, inv.numRows); 394 return result; 395 } 396 397 public Point getTotalWorkspacePadding() { 398 Rect padding = getWorkspacePadding(null); 399 return new Point(padding.left + padding.right, padding.top + padding.bottom); 400 } 401 402 /** 403 * Returns the workspace padding in the specified orientation. 404 * Note that it assumes that while in verticalBarLayout, the nav bar is on the right, as such 405 * this value is not reliable. 406 * Use {@link #getTotalWorkspacePadding()} instead. 407 */ 408 public Rect getWorkspacePadding(Rect recycle) { 409 Rect padding = recycle == null ? new Rect() : recycle; 410 if (isVerticalBarLayout()) { 411 if (mInsets.left > 0) { 412 padding.set(mInsets.left + pageIndicatorLandGutterLeftNavBarPx, 0, 413 hotseatBarHeightPx + hotseatLandGutterPx - mInsets.left, 2 * edgeMarginPx); 414 } else { 415 padding.set(pageIndicatorLandGutterRightNavBarPx, 0, 416 hotseatBarHeightPx + hotseatLandGutterPx, 2 * edgeMarginPx); 417 } 418 } else { 419 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx; 420 if (isTablet) { 421 // Pad the left and right of the workspace to ensure consistent spacing 422 // between all icons 423 int width = getCurrentWidth(); 424 int height = getCurrentHeight(); 425 // The amount of screen space available for left/right padding. 426 int availablePaddingX = Math.max(0, width - ((inv.numColumns * cellWidthPx) + 427 ((inv.numColumns - 1) * cellWidthPx))); 428 availablePaddingX = (int) Math.min(availablePaddingX, 429 width * MAX_HORIZONTAL_PADDING_PERCENT); 430 int availablePaddingY = Math.max(0, height - topWorkspacePadding - paddingBottom 431 - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx 432 - hotseatBarBottomPaddingPx); 433 padding.set(availablePaddingX / 2, topWorkspacePadding + availablePaddingY / 2, 434 availablePaddingX / 2, paddingBottom + availablePaddingY / 2); 435 } else { 436 // Pad the top and bottom of the workspace with search/hotseat bar sizes 437 padding.set(desiredWorkspaceLeftRightMarginPx, 438 topWorkspacePadding, 439 desiredWorkspaceLeftRightMarginPx, 440 paddingBottom); 441 } 442 } 443 return padding; 444 } 445 446 /** 447 * @return the bounds for which the open folders should be contained within 448 */ 449 public Rect getAbsoluteOpenFolderBounds() { 450 if (isVerticalBarLayout()) { 451 // Folders should only appear right of the drop target bar and left of the hotseat 452 return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx, 453 mInsets.top, 454 mInsets.left + availableWidthPx - hotseatBarHeightPx - edgeMarginPx, 455 mInsets.top + availableHeightPx); 456 } else { 457 // Folders should only appear below the drop target bar and above the hotseat 458 return new Rect(mInsets.left, 459 mInsets.top + dropTargetBarSizePx + edgeMarginPx, 460 mInsets.left + availableWidthPx, 461 mInsets.top + availableHeightPx - hotseatBarHeightPx - pageIndicatorHeightPx - 462 edgeMarginPx); 463 } 464 } 465 466 private int getWorkspacePageSpacing() { 467 if (isVerticalBarLayout() || isLargeTablet) { 468 // In landscape mode the page spacing is set to the default. 469 return defaultPageSpacingPx; 470 } else { 471 // In portrait, we want the pages spaced such that there is no 472 // overhang of the previous / next page into the current page viewport. 473 // We assume symmetrical padding in portrait mode. 474 return Math.max(defaultPageSpacingPx, getWorkspacePadding(null).left + 1); 475 } 476 } 477 478 int getOverviewModeButtonBarHeight() { 479 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx); 480 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx, 481 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight)); 482 return zoneHeight; 483 } 484 485 public static int calculateCellWidth(int width, int countX) { 486 return width / countX; 487 } 488 public static int calculateCellHeight(int height, int countY) { 489 return height / countY; 490 } 491 492 /** 493 * When {@code true}, the device is in landscape mode and the hotseat is on the right column. 494 * When {@code false}, either device is in portrait mode or the device is in landscape mode and 495 * the hotseat is on the bottom row. 496 */ 497 public boolean isVerticalBarLayout() { 498 return isLandscape && transposeLayoutWithOrientation; 499 } 500 501 boolean shouldFadeAdjacentWorkspaceScreens() { 502 return isVerticalBarLayout() || isLargeTablet; 503 } 504 505 private int getVisibleChildCount(ViewGroup parent) { 506 int visibleChildren = 0; 507 for (int i = 0; i < parent.getChildCount(); i++) { 508 if (parent.getChildAt(i).getVisibility() != View.GONE) { 509 visibleChildren++; 510 } 511 } 512 return visibleChildren; 513 } 514 515 public void layout(Launcher launcher, boolean notifyListeners) { 516 FrameLayout.LayoutParams lp; 517 boolean hasVerticalBarLayout = isVerticalBarLayout(); 518 519 // Layout the search bar space 520 Point searchBarBounds = getSearchBarDimensForWidgetOpts(); 521 View searchBar = launcher.getDropTargetBar(); 522 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams(); 523 lp.width = searchBarBounds.x; 524 lp.height = searchBarBounds.y; 525 lp.topMargin = mInsets.top + edgeMarginPx; 526 searchBar.setLayoutParams(lp); 527 528 // Layout the workspace 529 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace); 530 Rect workspacePadding = getWorkspacePadding(null); 531 workspace.setPadding(workspacePadding.left, workspacePadding.top, workspacePadding.right, 532 workspacePadding.bottom); 533 workspace.setPageSpacing(getWorkspacePageSpacing()); 534 535 // Only display when enabled 536 if (FeatureFlags.QSB_ON_FIRST_SCREEN) { 537 View qsbContainer = launcher.getQsbContainer(); 538 lp = (FrameLayout.LayoutParams) qsbContainer.getLayoutParams(); 539 lp.topMargin = mInsets.top + workspacePadding.top; 540 qsbContainer.setLayoutParams(lp); 541 } 542 543 // Layout the hotseat 544 Hotseat hotseat = (Hotseat) launcher.findViewById(R.id.hotseat); 545 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams(); 546 // We want the edges of the hotseat to line up with the edges of the workspace, but the 547 // icons in the hotseat are a different size, and so don't line up perfectly. To account for 548 // this, we pad the left and right of the hotseat with half of the difference of a workspace 549 // cell vs a hotseat cell. 550 float workspaceCellWidth = (float) getCurrentWidth() / inv.numColumns; 551 float hotseatCellWidth = (float) getCurrentWidth() / inv.numHotseatIcons; 552 int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2); 553 if (hasVerticalBarLayout) { 554 // Vertical hotseat -- The hotseat is fixed in the layout to be on the right of the 555 // screen regardless of RTL 556 lp.gravity = Gravity.RIGHT; 557 lp.width = hotseatBarHeightPx + mInsets.left + mInsets.right; 558 lp.height = LayoutParams.MATCH_PARENT; 559 hotseat.getLayout().setPadding(mInsets.left, mInsets.top, mInsets.right, 560 workspacePadding.bottom); 561 } else if (isTablet) { 562 // Pad the hotseat with the workspace padding calculated above 563 lp.gravity = Gravity.BOTTOM; 564 lp.width = LayoutParams.MATCH_PARENT; 565 lp.height = hotseatBarHeightPx + mInsets.bottom; 566 hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left, 567 hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right, 568 hotseatBarBottomPaddingPx + mInsets.bottom); 569 } else { 570 // For phones, layout the hotseat without any bottom margin 571 // to ensure that we have space for the folders 572 lp.gravity = Gravity.BOTTOM; 573 lp.width = LayoutParams.MATCH_PARENT; 574 lp.height = hotseatBarHeightPx + mInsets.bottom; 575 hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left, 576 hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right, 577 hotseatBarBottomPaddingPx + mInsets.bottom); 578 } 579 hotseat.setLayoutParams(lp); 580 581 // Layout the page indicators 582 View pageIndicator = launcher.findViewById(R.id.page_indicator); 583 if (pageIndicator != null) { 584 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams(); 585 if (isVerticalBarLayout()) { 586 if (mInsets.left > 0) { 587 lp.leftMargin = mInsets.left + pageIndicatorLandGutterLeftNavBarPx - 588 lp.width - pageIndicatorLandWorkspaceOffsetPx; 589 } else if (mInsets.right > 0) { 590 lp.leftMargin = pageIndicatorLandGutterRightNavBarPx - lp.width - 591 pageIndicatorLandWorkspaceOffsetPx; 592 } 593 lp.bottomMargin = workspacePadding.bottom; 594 } else { 595 // Put the page indicators above the hotseat 596 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; 597 lp.height = pageIndicatorHeightPx; 598 lp.bottomMargin = hotseatBarHeightPx + mInsets.bottom; 599 } 600 pageIndicator.setLayoutParams(lp); 601 } 602 603 // Layout the Overview Mode 604 ViewGroup overviewMode = launcher.getOverviewPanel(); 605 if (overviewMode != null) { 606 int visibleChildCount = getVisibleChildCount(overviewMode); 607 int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx; 608 int maxWidth = totalItemWidth + (visibleChildCount - 1) * overviewModeBarSpacerWidthPx; 609 610 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams(); 611 lp.width = Math.min(availableWidthPx, maxWidth); 612 lp.height = getOverviewModeButtonBarHeight() + mInsets.bottom; 613 overviewMode.setLayoutParams(lp); 614 } 615 616 if (notifyListeners) { 617 for (int i = mListeners.size() - 1; i >= 0; i--) { 618 mListeners.get(i).onLauncherLayoutChanged(); 619 } 620 } 621 } 622 623 private int getCurrentWidth() { 624 return isLandscape 625 ? Math.max(widthPx, heightPx) 626 : Math.min(widthPx, heightPx); 627 } 628 629 private int getCurrentHeight() { 630 return isLandscape 631 ? Math.min(widthPx, heightPx) 632 : Math.max(widthPx, heightPx); 633 } 634 635 public int getCellHeight(@ContainerType int containerType) { 636 switch (containerType) { 637 case CellLayout.WORKSPACE: 638 return cellHeightPx; 639 case CellLayout.FOLDER: 640 return folderCellHeightPx; 641 case CellLayout.HOTSEAT: 642 return hotseatCellHeightPx; 643 default: 644 // ?? 645 return 0; 646 } 647 } 648 649 /** 650 * @return the left/right paddings for all containers. 651 */ 652 public final int[] getContainerPadding() { 653 // No paddings for portrait phone 654 if (isPhone && !isVerticalBarLayout()) { 655 return new int[] {0, 0}; 656 } 657 658 // In landscape, we match the width of the workspace 659 int padding = (pageIndicatorLandGutterRightNavBarPx + 660 hotseatBarHeightPx + hotseatLandGutterPx + mInsets.left) / 2; 661 return new int[]{ padding, padding }; 662 } 663 664 public boolean shouldIgnoreLongPressToOverview(float touchX) { 665 boolean inMultiWindowMode = this != inv.landscapeProfile && this != inv.portraitProfile; 666 boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeMarginPx; 667 boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeMarginPx); 668 return !inMultiWindowMode && (touchedLhsEdge || touchedRhsEdge); 669 } 670 } 671