DeviceProfile.java revision ab946a19d41f245e27580281c3904e6c6636bcdd
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 17package com.android.launcher3; 18 19import android.appwidget.AppWidgetHostView; 20import android.content.ComponentName; 21import android.content.Context; 22import android.content.res.Resources; 23import android.graphics.Paint; 24import android.graphics.Paint.FontMetrics; 25import android.graphics.Point; 26import android.graphics.Rect; 27import android.util.DisplayMetrics; 28import android.view.Gravity; 29import android.view.View; 30import android.view.ViewGroup; 31import android.view.ViewGroup.LayoutParams; 32import android.view.ViewGroup.MarginLayoutParams; 33import android.widget.FrameLayout; 34import android.widget.LinearLayout; 35 36public class DeviceProfile { 37 38 public final InvariantDeviceProfile inv; 39 40 // Device properties 41 public final boolean isTablet; 42 public final boolean isLargeTablet; 43 public final boolean isPhone; 44 public final boolean transposeLayoutWithOrientation; 45 46 // Device properties in current orientation 47 public final boolean isLandscape; 48 public final int widthPx; 49 public final int heightPx; 50 public final int availableWidthPx; 51 public final int availableHeightPx; 52 53 // Overview mode 54 private final int overviewModeMinIconZoneHeightPx; 55 private final int overviewModeMaxIconZoneHeightPx; 56 private final int overviewModeBarItemWidthPx; 57 private final int overviewModeBarSpacerWidthPx; 58 private final float overviewModeIconZoneRatio; 59 60 // Workspace 61 private int desiredWorkspaceLeftRightMarginPx; 62 public final int edgeMarginPx; 63 public final Rect defaultWidgetPadding; 64 private final int pageIndicatorHeightPx; 65 private final int defaultPageSpacingPx; 66 private float dragViewScale; 67 68 // Workspace icons 69 public int iconSizePx; 70 public int iconTextSizePx; 71 public int iconDrawablePaddingPx; 72 public int iconDrawablePaddingOriginalPx; 73 74 public int cellWidthPx; 75 public int cellHeightPx; 76 77 // Folder 78 public int folderBackgroundOffset; 79 public int folderIconSizePx; 80 public int folderCellWidthPx; 81 public int folderCellHeightPx; 82 83 // Hotseat 84 public int hotseatCellWidthPx; 85 public int hotseatCellHeightPx; 86 public int hotseatIconSizePx; 87 private int hotseatBarHeightPx; 88 89 // All apps 90 public int allAppsNumCols; 91 public int allAppsNumPredictiveCols; 92 public int allAppsButtonVisualSize; 93 public final int allAppsIconSizePx; 94 public final int allAppsIconTextSizePx; 95 96 // QSB 97 private int searchBarSpaceWidthPx; 98 private int searchBarSpaceHeightPx; 99 100 public DeviceProfile(Context context, InvariantDeviceProfile inv, 101 Point minSize, Point maxSize, 102 int width, int height, boolean isLandscape) { 103 104 this.inv = inv; 105 this.isLandscape = isLandscape; 106 107 Resources res = context.getResources(); 108 DisplayMetrics dm = res.getDisplayMetrics(); 109 110 // Constants from resources 111 isTablet = res.getBoolean(R.bool.is_tablet); 112 isLargeTablet = res.getBoolean(R.bool.is_large_tablet); 113 isPhone = !isTablet && !isLargeTablet; 114 115 // Some more constants 116 transposeLayoutWithOrientation = 117 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation); 118 119 ComponentName cn = new ComponentName(context.getPackageName(), 120 this.getClass().getName()); 121 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null); 122 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin); 123 desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx; 124 pageIndicatorHeightPx = 125 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height); 126 defaultPageSpacingPx = 127 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing); 128 overviewModeMinIconZoneHeightPx = 129 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height); 130 overviewModeMaxIconZoneHeightPx = 131 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height); 132 overviewModeBarItemWidthPx = 133 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width); 134 overviewModeBarSpacerWidthPx = 135 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width); 136 overviewModeIconZoneRatio = 137 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f; 138 iconDrawablePaddingOriginalPx = 139 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding); 140 141 // AllApps uses the original non-scaled icon text size 142 allAppsIconTextSizePx = Utilities.pxFromDp(inv.iconTextSize, dm); 143 144 // AllApps uses the original non-scaled icon size 145 allAppsIconSizePx = Utilities.pxFromDp(inv.iconSize, dm); 146 147 // Determine sizes. 148 widthPx = width; 149 heightPx = height; 150 if (isLandscape) { 151 availableWidthPx = maxSize.x; 152 availableHeightPx = minSize.y; 153 } else { 154 availableWidthPx = minSize.x; 155 availableHeightPx = maxSize.y; 156 } 157 158 // Calculate the remaining vars 159 updateAvailableDimensions(dm, res); 160 computeAllAppsButtonSize(context); 161 } 162 163 /** 164 * Determine the exact visual footprint of the all apps button, taking into account scaling 165 * and internal padding of the drawable. 166 */ 167 private void computeAllAppsButtonSize(Context context) { 168 Resources res = context.getResources(); 169 float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f; 170 allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding)); 171 } 172 173 private void updateAvailableDimensions(DisplayMetrics dm, Resources res) { 174 // Check to see if the icons fit in the new available height. If not, then we need to 175 // shrink the icon size. 176 float scale = 1f; 177 int drawablePadding = iconDrawablePaddingOriginalPx; 178 updateIconSize(1f, drawablePadding, res, dm); 179 float usedHeight = (cellHeightPx * inv.numRows); 180 181 // We only care about the top and bottom workspace padding, which is not affected by RTL. 182 Rect workspacePadding = getWorkspacePadding(false /* isLayoutRtl */); 183 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom); 184 if (usedHeight > maxHeight) { 185 scale = maxHeight / usedHeight; 186 drawablePadding = 0; 187 } 188 updateIconSize(scale, drawablePadding, res, dm); 189 } 190 191 private void updateIconSize(float scale, int drawablePadding, Resources res, 192 DisplayMetrics dm) { 193 iconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale); 194 iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale); 195 iconDrawablePaddingPx = drawablePadding; 196 hotseatIconSizePx = (int) (Utilities.pxFromDp(inv.hotseatIconSize, dm) * scale); 197 198 // Search Bar 199 searchBarSpaceWidthPx = Math.min(widthPx, 200 res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width)); 201 searchBarSpaceHeightPx = getSearchBarTopOffset() 202 + res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height); 203 204 // Calculate the actual text height 205 Paint textPaint = new Paint(); 206 textPaint.setTextSize(iconTextSizePx); 207 FontMetrics fm = textPaint.getFontMetrics(); 208 cellWidthPx = iconSizePx; 209 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top); 210 final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale); 211 dragViewScale = (iconSizePx + scaleDps) / iconSizePx; 212 213 // Hotseat 214 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx; 215 hotseatCellWidthPx = iconSizePx; 216 hotseatCellHeightPx = iconSizePx; 217 218 // Folder 219 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx; 220 folderCellHeightPx = cellHeightPx + edgeMarginPx; 221 folderBackgroundOffset = -edgeMarginPx; 222 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset; 223 } 224 225 /** 226 * @param recyclerViewWidth the available width of the AllAppsRecyclerView 227 */ 228 public void updateAppsViewNumCols(Resources res, int recyclerViewWidth) { 229 int appsViewLeftMarginPx = 230 res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin); 231 int allAppsCellWidthGap = 232 res.getDimensionPixelSize(R.dimen.all_apps_icon_width_gap); 233 int availableAppsWidthPx = (recyclerViewWidth > 0) ? recyclerViewWidth : availableWidthPx; 234 int numAppsCols = (availableAppsWidthPx - appsViewLeftMarginPx) / 235 (allAppsIconSizePx + allAppsCellWidthGap); 236 int numPredictiveAppCols = Math.max(inv.minAllAppsPredictionColumns, numAppsCols); 237 allAppsNumCols = numAppsCols; 238 allAppsNumPredictiveCols = numPredictiveAppCols; 239 } 240 241 /** Returns the search bar top offset */ 242 private int getSearchBarTopOffset() { 243 if (isTablet && !isVerticalBarLayout()) { 244 return 4 * edgeMarginPx; 245 } else { 246 return 2 * edgeMarginPx; 247 } 248 } 249 250 /** Returns the search bar bounds in the current orientation */ 251 public Rect getSearchBarBounds(boolean isLayoutRtl) { 252 Rect bounds = new Rect(); 253 if (isLandscape && transposeLayoutWithOrientation) { 254 if (isLayoutRtl) { 255 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx, 256 availableWidthPx, availableHeightPx - edgeMarginPx); 257 } else { 258 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx, 259 availableHeightPx - edgeMarginPx); 260 } 261 } else { 262 if (isTablet) { 263 // Pad the left and right of the workspace to ensure consistent spacing 264 // between all icons 265 int width = getCurrentWidth(); 266 // XXX: If the icon size changes across orientations, we will have to take 267 // that into account here too. 268 int gap = (int) ((width - 2 * edgeMarginPx - 269 (inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1))); 270 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(), 271 availableWidthPx - (edgeMarginPx + gap), 272 searchBarSpaceHeightPx); 273 } else { 274 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left, 275 getSearchBarTopOffset(), 276 availableWidthPx - (desiredWorkspaceLeftRightMarginPx - 277 defaultWidgetPadding.right), searchBarSpaceHeightPx); 278 } 279 } 280 return bounds; 281 } 282 283 public Point getCellSize() { 284 Point result = new Point(); 285 // Since we are only concerned with the overall padding, layout direction does 286 // not matter. 287 Rect padding = getWorkspacePadding(false /* isLayoutRtl */ ); 288 result.x = calculateCellWidth(availableWidthPx - padding.left - padding.right, 289 inv.numColumns); 290 result.y = calculateCellHeight(availableHeightPx - padding.top - padding.bottom, 291 inv.numRows); 292 return result; 293 } 294 295 /** Returns the workspace padding in the specified orientation */ 296 Rect getWorkspacePadding(boolean isLayoutRtl) { 297 Rect searchBarBounds = getSearchBarBounds(isLayoutRtl); 298 Rect padding = new Rect(); 299 if (isLandscape && transposeLayoutWithOrientation) { 300 // Pad the left and right of the workspace with search/hotseat bar sizes 301 if (isLayoutRtl) { 302 padding.set(hotseatBarHeightPx, edgeMarginPx, 303 searchBarBounds.width(), edgeMarginPx); 304 } else { 305 padding.set(searchBarBounds.width(), edgeMarginPx, 306 hotseatBarHeightPx, edgeMarginPx); 307 } 308 } else { 309 if (isTablet) { 310 // Pad the left and right of the workspace to ensure consistent spacing 311 // between all icons 312 float gapScale = 1f + (dragViewScale - 1f) / 2f; 313 int width = getCurrentWidth(); 314 int height = getCurrentHeight(); 315 int paddingTop = searchBarBounds.bottom; 316 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx; 317 int availableWidth = Math.max(0, width - (int) ((inv.numColumns * cellWidthPx) + 318 (inv.numColumns * gapScale * cellWidthPx))); 319 int availableHeight = Math.max(0, height - paddingTop - paddingBottom 320 - (int) (2 * inv.numRows * cellHeightPx)); 321 padding.set(availableWidth / 2, paddingTop + availableHeight / 2, 322 availableWidth / 2, paddingBottom + availableHeight / 2); 323 } else { 324 // Pad the top and bottom of the workspace with search/hotseat bar sizes 325 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left, 326 searchBarBounds.bottom, 327 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right, 328 hotseatBarHeightPx + pageIndicatorHeightPx); 329 } 330 } 331 return padding; 332 } 333 334 private int getWorkspacePageSpacing(boolean isLayoutRtl) { 335 if ((isLandscape && transposeLayoutWithOrientation) || isLargeTablet) { 336 // In landscape mode the page spacing is set to the default. 337 return defaultPageSpacingPx; 338 } else { 339 // In portrait, we want the pages spaced such that there is no 340 // overhang of the previous / next page into the current page viewport. 341 // We assume symmetrical padding in portrait mode. 342 return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding(isLayoutRtl).left); 343 } 344 } 345 346 int getOverviewModeButtonBarHeight() { 347 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx); 348 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx, 349 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight)); 350 return zoneHeight; 351 } 352 353 // The rect returned will be extended to below the system ui that covers the workspace 354 public boolean isInHotseatRect(int x, int y) { 355 if (isVerticalBarLayout()) { 356 return (x >= (availableWidthPx - hotseatBarHeightPx)) 357 && (y >= 0) && (y <= availableHeightPx); 358 } else { 359 return (x >= 0) && (x <= availableWidthPx) 360 && (y >= (availableHeightPx - hotseatBarHeightPx)); 361 } 362 } 363 364 public static int calculateCellWidth(int width, int countX) { 365 return width / countX; 366 } 367 public static int calculateCellHeight(int height, int countY) { 368 return height / countY; 369 } 370 371 /** 372 * When {@code true}, hotseat is on the bottom row when in landscape mode. 373 * If {@code false}, hotseat is on the right column when in landscape mode. 374 */ 375 public boolean isVerticalBarLayout() { 376 return isLandscape && transposeLayoutWithOrientation; 377 } 378 379 boolean shouldFadeAdjacentWorkspaceScreens() { 380 return isVerticalBarLayout() || isLargeTablet; 381 } 382 383 private int getVisibleChildCount(ViewGroup parent) { 384 int visibleChildren = 0; 385 for (int i = 0; i < parent.getChildCount(); i++) { 386 if (parent.getChildAt(i).getVisibility() != View.GONE) { 387 visibleChildren++; 388 } 389 } 390 return visibleChildren; 391 } 392 393 public void layout(Launcher launcher) { 394 FrameLayout.LayoutParams lp; 395 boolean hasVerticalBarLayout = isVerticalBarLayout(); 396 final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources()); 397 398 // Layout the search bar space 399 View searchBar = launcher.getSearchDropTargetBar(); 400 lp = getDropTargetBarLayoutParams(hasVerticalBarLayout, searchBar, Gravity.TOP); 401 searchBar.setLayoutParams(lp); 402 403 // Layout the app info bar space 404 View appInfoBar = launcher.getAppInfoDropTargetBar(); 405 lp = getDropTargetBarLayoutParams(hasVerticalBarLayout, appInfoBar, Gravity.BOTTOM); 406 lp.bottomMargin = hotseatBarHeightPx; 407 appInfoBar.setLayoutParams(lp); 408 409 // Layout the workspace 410 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace); 411 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams(); 412 lp.gravity = Gravity.CENTER; 413 Rect padding = getWorkspacePadding(isLayoutRtl); 414 workspace.setLayoutParams(lp); 415 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom); 416 workspace.setPageSpacing(getWorkspacePageSpacing(isLayoutRtl)); 417 418 // Layout the hotseat 419 View hotseat = launcher.findViewById(R.id.hotseat); 420 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams(); 421 if (hasVerticalBarLayout) { 422 // Vertical hotseat -- The hotseat is fixed in the layout to be on the right of the 423 // screen regardless of RTL 424 lp.gravity = Gravity.RIGHT; 425 lp.width = hotseatBarHeightPx; 426 lp.height = LayoutParams.MATCH_PARENT; 427 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx); 428 } else if (isTablet) { 429 // Pad the hotseat with the workspace padding calculated above 430 lp.gravity = Gravity.BOTTOM; 431 lp.width = LayoutParams.MATCH_PARENT; 432 lp.height = hotseatBarHeightPx; 433 hotseat.setPadding(edgeMarginPx + padding.left, 0, 434 edgeMarginPx + padding.right, 435 2 * edgeMarginPx); 436 } else { 437 // For phones, layout the hotseat without any bottom margin 438 // to ensure that we have space for the folders 439 lp.gravity = Gravity.BOTTOM; 440 lp.width = LayoutParams.MATCH_PARENT; 441 lp.height = hotseatBarHeightPx; 442 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0, 443 2 * edgeMarginPx, 0); 444 } 445 hotseat.setLayoutParams(lp); 446 447 // Layout the page indicators 448 View pageIndicator = launcher.findViewById(R.id.page_indicator); 449 if (pageIndicator != null) { 450 if (hasVerticalBarLayout) { 451 // Hide the page indicators when we have vertical search/hotseat 452 pageIndicator.setVisibility(View.GONE); 453 } else { 454 // Put the page indicators above the hotseat 455 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams(); 456 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; 457 lp.width = LayoutParams.WRAP_CONTENT; 458 lp.height = LayoutParams.WRAP_CONTENT; 459 lp.bottomMargin = hotseatBarHeightPx; 460 pageIndicator.setLayoutParams(lp); 461 } 462 } 463 464 // Layout the Overview Mode 465 ViewGroup overviewMode = launcher.getOverviewPanel(); 466 if (overviewMode != null) { 467 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams(); 468 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; 469 470 int visibleChildCount = getVisibleChildCount(overviewMode); 471 int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx; 472 int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx; 473 474 lp.width = Math.min(availableWidthPx, maxWidth); 475 lp.height = getOverviewModeButtonBarHeight(); 476 overviewMode.setLayoutParams(lp); 477 478 if (lp.width > totalItemWidth && visibleChildCount > 1) { 479 // We have enough space. Lets add some margin too. 480 int margin = (lp.width - totalItemWidth) / (visibleChildCount-1); 481 View lastChild = null; 482 483 // Set margin of all visible children except the last visible child 484 for (int i = 0; i < visibleChildCount; i++) { 485 if (lastChild != null) { 486 MarginLayoutParams clp = (MarginLayoutParams) lastChild.getLayoutParams(); 487 if (isLayoutRtl) { 488 clp.leftMargin = margin; 489 } else { 490 clp.rightMargin = margin; 491 } 492 lastChild.setLayoutParams(clp); 493 lastChild = null; 494 } 495 View thisChild = overviewMode.getChildAt(i); 496 if (thisChild.getVisibility() != View.GONE) { 497 lastChild = thisChild; 498 } 499 } 500 } 501 } 502 } 503 504 private FrameLayout.LayoutParams getDropTargetBarLayoutParams(boolean hasVerticalBarLayout, 505 View dropTargetBar, int verticalGravity) { 506 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) dropTargetBar.getLayoutParams(); 507 if (hasVerticalBarLayout) { 508 // Vertical drop target bar space -- The drop target bar is fixed in the layout to be on 509 // the left of the screen regardless of RTL 510 lp.gravity = Gravity.LEFT; 511 lp.width = searchBarSpaceHeightPx; 512 513 LinearLayout targets = (LinearLayout) dropTargetBar.findViewById(R.id.drag_target_bar); 514 targets.setOrientation(LinearLayout.VERTICAL); 515 FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams(); 516 targetsLp.gravity = verticalGravity; 517 targetsLp.height = LayoutParams.WRAP_CONTENT; 518 } else { 519 // Horizontal drop target bar space 520 lp.gravity = verticalGravity; 521 lp.height = searchBarSpaceHeightPx; 522 523 LinearLayout targets = (LinearLayout) dropTargetBar.findViewById(R.id.drag_target_bar); 524 targets.getLayoutParams().width = searchBarSpaceWidthPx; 525 } 526 return lp; 527 } 528 529 private int getCurrentWidth() { 530 return isLandscape 531 ? Math.max(widthPx, heightPx) 532 : Math.min(widthPx, heightPx); 533 } 534 535 private int getCurrentHeight() { 536 return isLandscape 537 ? Math.min(widthPx, heightPx) 538 : Math.max(widthPx, heightPx); 539 } 540} 541