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 { 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 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 DeviceProfile(Context context, InvariantDeviceProfile inv, Point minSize, Point maxSize, int width, int height, boolean isLandscape)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 getMultiWindowProfile(Context context, Point mwSize)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 addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener)292 public void addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) { 293 if (!mListeners.contains(listener)) { 294 mListeners.add(listener); 295 } 296 } 297 removeLauncherLayoutChangedListener(LauncherLayoutChangeListener listener)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 */ adjustToHideWorkspaceLabels()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 */ computeAllAppsButtonSize(Context context)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 updateAvailableDimensions(DisplayMetrics dm, Resources res)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 updateIconSize(float scale, Resources res, DisplayMetrics dm)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 updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res)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 updateFolderCellSize(float scale, DisplayMetrics dm, Resources res)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 updateInsets(Rect insets)442 public void updateInsets(Rect insets) { 443 mInsets.set(insets); 444 } 445 updateAppsViewNumCols()446 public void updateAppsViewNumCols() { 447 allAppsNumCols = allAppsNumPredictiveCols = inv.numColumns; 448 } 449 450 /** Returns the width and height of the search bar, ignoring any padding. */ getSearchBarDimensForWidgetOpts()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 getCellSize()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 getTotalWorkspacePadding()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 */ getWorkspacePadding(Rect recycle)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 */ getAbsoluteOpenFolderBounds()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 getWorkspacePageSpacing()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 getOverviewModeButtonBarHeight()569 int getOverviewModeButtonBarHeight() { 570 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx); 571 return Utilities.boundToRange(zoneHeight, 572 overviewModeMinIconZoneHeightPx, 573 overviewModeMaxIconZoneHeightPx); 574 } 575 calculateCellWidth(int width, int countX)576 public static int calculateCellWidth(int width, int countX) { 577 return width / countX; 578 } calculateCellHeight(int height, int countY)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 */ isVerticalBarLayout()588 public boolean isVerticalBarLayout() { 589 return isLandscape && transposeLayoutWithOrientation; 590 } 591 shouldFadeAdjacentWorkspaceScreens()592 boolean shouldFadeAdjacentWorkspaceScreens() { 593 return isVerticalBarLayout() || isLargeTablet; 594 } 595 getVisibleChildCount(ViewGroup parent)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 layout(Launcher launcher, boolean notifyListeners)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 getCurrentWidth()727 private int getCurrentWidth() { 728 return isLandscape 729 ? Math.max(widthPx, heightPx) 730 : Math.min(widthPx, heightPx); 731 } 732 getCurrentHeight()733 private int getCurrentHeight() { 734 return isLandscape 735 ? Math.min(widthPx, heightPx) 736 : Math.max(widthPx, heightPx); 737 } 738 getCellHeight(@ontainerType int containerType)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 */ getContainerPadding()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 inMultiWindowMode()767 public boolean inMultiWindowMode() { 768 return this != inv.landscapeProfile && this != inv.portraitProfile; 769 } 770 shouldIgnoreLongPressToOverview(float touchX)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 getContext(Context c, int orientation)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