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