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 { onLauncherLayoutChanged()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 DeviceProfile(Context context, InvariantDeviceProfile inv, Point minSize, Point maxSize, int width, int height, boolean isLandscape)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 getMultiWindowProfile(Context context, Point mwSize)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 addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener)243 public void addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) { 244 if (!mListeners.contains(listener)) { 245 mListeners.add(listener); 246 } 247 } 248 removeLauncherLayoutChangedListener(LauncherLayoutChangeListener listener)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 */ computeAllAppsButtonSize(Context context)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 updateAvailableDimensions(DisplayMetrics dm, Resources res)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 updateIconSize(float scale, int drawablePadding, Resources res, DisplayMetrics dm)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 updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res)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 updateFolderCellSize(float scale, DisplayMetrics dm, Resources res)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 updateInsets(Rect insets)357 public void updateInsets(Rect insets) { 358 mInsets.set(insets); 359 } 360 updateAppsViewNumCols()361 public void updateAppsViewNumCols() { 362 allAppsNumCols = allAppsNumPredictiveCols = inv.numColumns; 363 } 364 365 /** Returns the width and height of the search bar, ignoring any padding. */ getSearchBarDimensForWidgetOpts()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 getCellSize()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 getTotalWorkspacePadding()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 */ getWorkspacePadding(Rect recycle)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 */ getAbsoluteOpenFolderBounds()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 getWorkspacePageSpacing()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 getOverviewModeButtonBarHeight()478 int getOverviewModeButtonBarHeight() { 479 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx); 480 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx, 481 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight)); 482 return zoneHeight; 483 } 484 calculateCellWidth(int width, int countX)485 public static int calculateCellWidth(int width, int countX) { 486 return width / countX; 487 } calculateCellHeight(int height, int countY)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 */ isVerticalBarLayout()497 public boolean isVerticalBarLayout() { 498 return isLandscape && transposeLayoutWithOrientation; 499 } 500 shouldFadeAdjacentWorkspaceScreens()501 boolean shouldFadeAdjacentWorkspaceScreens() { 502 return isVerticalBarLayout() || isLargeTablet; 503 } 504 getVisibleChildCount(ViewGroup parent)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 layout(Launcher launcher, boolean notifyListeners)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 getCurrentWidth()623 private int getCurrentWidth() { 624 return isLandscape 625 ? Math.max(widthPx, heightPx) 626 : Math.min(widthPx, heightPx); 627 } 628 getCurrentHeight()629 private int getCurrentHeight() { 630 return isLandscape 631 ? Math.min(widthPx, heightPx) 632 : Math.max(widthPx, heightPx); 633 } 634 getCellHeight(@ontainerType int containerType)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 */ getContainerPadding()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 shouldIgnoreLongPressToOverview(float touchX)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