DeviceProfile.java revision d017f882eb67b630adb082dd2227e20f5bc77b05
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.Configuration; 23import android.content.res.Resources; 24import android.graphics.Paint; 25import android.graphics.Paint.FontMetrics; 26import android.graphics.Point; 27import android.graphics.PointF; 28import android.graphics.Rect; 29import android.util.DisplayMetrics; 30import android.view.Display; 31import android.view.Gravity; 32import android.view.Surface; 33import android.view.View; 34import android.view.ViewGroup; 35import android.view.ViewGroup.LayoutParams; 36import android.view.WindowManager; 37import android.widget.FrameLayout; 38 39import java.util.ArrayList; 40import java.util.Collections; 41import java.util.Comparator; 42 43 44class DeviceProfileQuery { 45 float widthDps; 46 float heightDps; 47 float value; 48 PointF dimens; 49 50 DeviceProfileQuery(float w, float h, float v) { 51 widthDps = w; 52 heightDps = h; 53 value = v; 54 dimens = new PointF(w, h); 55 } 56} 57 58public class DeviceProfile { 59 public static interface DeviceProfileCallbacks { 60 public void onAvailableSizeChanged(DeviceProfile grid); 61 } 62 63 String name; 64 float minWidthDps; 65 float minHeightDps; 66 float numRows; 67 float numColumns; 68 float numHotseatIcons; 69 private float iconSize; 70 private float iconTextSize; 71 private int iconDrawablePaddingOriginalPx; 72 private float hotseatIconSize; 73 74 boolean isLandscape; 75 boolean isTablet; 76 boolean isLargeTablet; 77 boolean isLayoutRtl; 78 boolean transposeLayoutWithOrientation; 79 80 int desiredWorkspaceLeftRightMarginPx; 81 int edgeMarginPx; 82 Rect defaultWidgetPadding; 83 84 int widthPx; 85 int heightPx; 86 int availableWidthPx; 87 int availableHeightPx; 88 int defaultPageSpacingPx; 89 90 int overviewModeMinIconZoneHeightPx; 91 int overviewModeMaxIconZoneHeightPx; 92 int overviewModeBarItemWidthPx; 93 int overviewModeBarSpacerWidthPx; 94 float overviewModeIconZoneRatio; 95 float overviewModeScaleFactor; 96 97 int iconSizePx; 98 int iconTextSizePx; 99 int iconDrawablePaddingPx; 100 int cellWidthPx; 101 int cellHeightPx; 102 int allAppsIconSizePx; 103 int allAppsIconTextSizePx; 104 int allAppsCellWidthPx; 105 int allAppsCellHeightPx; 106 int allAppsCellPaddingPx; 107 int folderBackgroundOffset; 108 int folderIconSizePx; 109 int folderCellWidthPx; 110 int folderCellHeightPx; 111 int hotseatCellWidthPx; 112 int hotseatCellHeightPx; 113 int hotseatIconSizePx; 114 int hotseatBarHeightPx; 115 int hotseatAllAppsRank; 116 int allAppsNumRows; 117 int allAppsNumCols; 118 int searchBarSpaceWidthPx; 119 int searchBarSpaceMaxWidthPx; 120 int searchBarSpaceHeightPx; 121 int searchBarHeightPx; 122 int pageIndicatorHeightPx; 123 124 float dragViewScale; 125 126 private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>(); 127 128 DeviceProfile(String n, float w, float h, float r, float c, 129 float is, float its, float hs, float his) { 130 // Ensure that we have an odd number of hotseat items (since we need to place all apps) 131 if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) { 132 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces"); 133 } 134 135 name = n; 136 minWidthDps = w; 137 minHeightDps = h; 138 numRows = r; 139 numColumns = c; 140 iconSize = is; 141 iconTextSize = its; 142 numHotseatIcons = hs; 143 hotseatIconSize = his; 144 } 145 146 DeviceProfile(Context context, 147 ArrayList<DeviceProfile> profiles, 148 float minWidth, float minHeight, 149 int wPx, int hPx, 150 int awPx, int ahPx, 151 Resources res) { 152 DisplayMetrics dm = res.getDisplayMetrics(); 153 ArrayList<DeviceProfileQuery> points = 154 new ArrayList<DeviceProfileQuery>(); 155 transposeLayoutWithOrientation = 156 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation); 157 minWidthDps = minWidth; 158 minHeightDps = minHeight; 159 160 ComponentName cn = new ComponentName(context.getPackageName(), 161 this.getClass().getName()); 162 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null); 163 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin); 164 desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx; 165 pageIndicatorHeightPx = 166 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height); 167 defaultPageSpacingPx = 168 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing); 169 allAppsCellPaddingPx = 170 res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding); 171 overviewModeMinIconZoneHeightPx = 172 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height); 173 overviewModeMaxIconZoneHeightPx = 174 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height); 175 overviewModeBarItemWidthPx = 176 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width); 177 overviewModeBarSpacerWidthPx = 178 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width); 179 overviewModeIconZoneRatio = 180 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f; 181 overviewModeScaleFactor = 182 res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f; 183 184 // Interpolate the rows 185 for (DeviceProfile p : profiles) { 186 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows)); 187 } 188 numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points)); 189 // Interpolate the columns 190 points.clear(); 191 for (DeviceProfile p : profiles) { 192 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns)); 193 } 194 numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points)); 195 // Interpolate the hotseat length 196 points.clear(); 197 for (DeviceProfile p : profiles) { 198 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons)); 199 } 200 numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points)); 201 hotseatAllAppsRank = (int) (numHotseatIcons / 2); 202 203 // Interpolate the icon size 204 points.clear(); 205 for (DeviceProfile p : profiles) { 206 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize)); 207 } 208 iconSize = invDistWeightedInterpolate(minWidth, minHeight, points); 209 // AllApps uses the original non-scaled icon size 210 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm); 211 212 // Interpolate the icon text size 213 points.clear(); 214 for (DeviceProfile p : profiles) { 215 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize)); 216 } 217 iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points); 218 iconDrawablePaddingOriginalPx = 219 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding); 220 // AllApps uses the original non-scaled icon text size 221 allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm); 222 223 // Interpolate the hotseat icon size 224 points.clear(); 225 for (DeviceProfile p : profiles) { 226 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize)); 227 } 228 // Hotseat 229 hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points); 230 231 // Calculate the remaining vars 232 updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx); 233 updateAvailableDimensions(context); 234 } 235 236 void addCallback(DeviceProfileCallbacks cb) { 237 mCallbacks.add(cb); 238 cb.onAvailableSizeChanged(this); 239 } 240 void removeCallback(DeviceProfileCallbacks cb) { 241 mCallbacks.remove(cb); 242 } 243 244 private int getDeviceOrientation(Context context) { 245 WindowManager windowManager = (WindowManager) 246 context.getSystemService(Context.WINDOW_SERVICE); 247 Resources resources = context.getResources(); 248 DisplayMetrics dm = resources.getDisplayMetrics(); 249 Configuration config = resources.getConfiguration(); 250 int rotation = windowManager.getDefaultDisplay().getRotation(); 251 252 boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) && 253 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180); 254 boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) && 255 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270); 256 if (isLandscape || isRotatedPortrait) { 257 return CellLayout.LANDSCAPE; 258 } else { 259 return CellLayout.PORTRAIT; 260 } 261 } 262 263 private void updateAvailableDimensions(Context context) { 264 WindowManager windowManager = (WindowManager) 265 context.getSystemService(Context.WINDOW_SERVICE); 266 Display display = windowManager.getDefaultDisplay(); 267 Resources resources = context.getResources(); 268 DisplayMetrics dm = resources.getDisplayMetrics(); 269 Configuration config = resources.getConfiguration(); 270 271 // There are three possible configurations that the dynamic grid accounts for, portrait, 272 // landscape with the nav bar at the bottom, and landscape with the nav bar at the side. 273 // To prevent waiting for fitSystemWindows(), we make the observation that in landscape, 274 // the height is the smallest height (either with the nav bar at the bottom or to the 275 // side) and otherwise, the height is simply the largest possible height for a portrait 276 // device. 277 Point size = new Point(); 278 Point smallestSize = new Point(); 279 Point largestSize = new Point(); 280 display.getSize(size); 281 display.getCurrentSizeRange(smallestSize, largestSize); 282 availableWidthPx = size.x; 283 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { 284 availableHeightPx = smallestSize.y; 285 } else { 286 availableHeightPx = largestSize.y; 287 } 288 289 // Check to see if the icons fit in the new available height. If not, then we need to 290 // shrink the icon size. 291 float scale = 1f; 292 int drawablePadding = iconDrawablePaddingOriginalPx; 293 updateIconSize(1f, drawablePadding, resources, dm); 294 float usedHeight = (cellHeightPx * numRows); 295 296 Rect workspacePadding = getWorkspacePadding(); 297 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom); 298 if (usedHeight > maxHeight) { 299 scale = maxHeight / usedHeight; 300 drawablePadding = 0; 301 } 302 updateIconSize(scale, drawablePadding, resources, dm); 303 304 // Make the callbacks 305 for (DeviceProfileCallbacks cb : mCallbacks) { 306 cb.onAvailableSizeChanged(this); 307 } 308 } 309 310 private void updateIconSize(float scale, int drawablePadding, Resources resources, 311 DisplayMetrics dm) { 312 iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale); 313 iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale); 314 iconDrawablePaddingPx = drawablePadding; 315 hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale); 316 317 // Search Bar 318 searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width); 319 searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height); 320 searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx); 321 searchBarSpaceHeightPx = searchBarHeightPx + getSearchBarTopOffset(); 322 323 // Calculate the actual text height 324 Paint textPaint = new Paint(); 325 textPaint.setTextSize(iconTextSizePx); 326 FontMetrics fm = textPaint.getFontMetrics(); 327 cellWidthPx = iconSizePx; 328 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top); 329 final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale); 330 dragViewScale = (iconSizePx + scaleDps) / iconSizePx; 331 332 // Hotseat 333 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx; 334 hotseatCellWidthPx = iconSizePx; 335 hotseatCellHeightPx = iconSizePx; 336 337 // Folder 338 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx; 339 folderCellHeightPx = cellHeightPx + edgeMarginPx; 340 folderBackgroundOffset = -edgeMarginPx; 341 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset; 342 343 // All Apps 344 Rect padding = getWorkspacePadding(isLandscape ? 345 CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 346 int pageIndicatorOffset = 347 resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset); 348 allAppsCellWidthPx = allAppsIconSizePx; 349 allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx; 350 int maxLongEdgeCellCount = 351 resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count); 352 int maxShortEdgeCellCount = 353 resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count); 354 int minEdgeCellCount = 355 resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count); 356 int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount); 357 int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount); 358 359 allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) / 360 (allAppsCellHeightPx + allAppsCellPaddingPx); 361 allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows)); 362 allAppsNumCols = (availableWidthPx) / 363 (allAppsCellWidthPx + allAppsCellPaddingPx); 364 allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols)); 365 } 366 367 void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx, 368 int awPx, int ahPx) { 369 Configuration configuration = resources.getConfiguration(); 370 isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE); 371 isTablet = resources.getBoolean(R.bool.is_tablet); 372 isLargeTablet = resources.getBoolean(R.bool.is_large_tablet); 373 isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL); 374 widthPx = wPx; 375 heightPx = hPx; 376 availableWidthPx = awPx; 377 availableHeightPx = ahPx; 378 379 updateAvailableDimensions(context); 380 } 381 382 private float dist(PointF p0, PointF p1) { 383 return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) + 384 (p1.y-p0.y)*(p1.y-p0.y)); 385 } 386 387 private float weight(PointF a, PointF b, 388 float pow) { 389 float d = dist(a, b); 390 if (d == 0f) { 391 return Float.POSITIVE_INFINITY; 392 } 393 return (float) (1f / Math.pow(d, pow)); 394 } 395 396 private float invDistWeightedInterpolate(float width, float height, 397 ArrayList<DeviceProfileQuery> points) { 398 float sum = 0; 399 float weights = 0; 400 float pow = 5; 401 float kNearestNeighbors = 3; 402 final PointF xy = new PointF(width, height); 403 404 ArrayList<DeviceProfileQuery> pointsByNearness = points; 405 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() { 406 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) { 407 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens)); 408 } 409 }); 410 411 for (int i = 0; i < pointsByNearness.size(); ++i) { 412 DeviceProfileQuery p = pointsByNearness.get(i); 413 if (i < kNearestNeighbors) { 414 float w = weight(xy, p.dimens, pow); 415 if (w == Float.POSITIVE_INFINITY) { 416 return p.value; 417 } 418 weights += w; 419 } 420 } 421 422 for (int i = 0; i < pointsByNearness.size(); ++i) { 423 DeviceProfileQuery p = pointsByNearness.get(i); 424 if (i < kNearestNeighbors) { 425 float w = weight(xy, p.dimens, pow); 426 sum += w * p.value / weights; 427 } 428 } 429 430 return sum; 431 } 432 433 /** Returns the search bar top offset */ 434 int getSearchBarTopOffset() { 435 if (isTablet() && !isVerticalBarLayout()) { 436 return 4 * edgeMarginPx; 437 } else { 438 return 2 * edgeMarginPx; 439 } 440 } 441 442 /** Returns the search bar bounds in the current orientation */ 443 Rect getSearchBarBounds() { 444 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 445 } 446 /** Returns the search bar bounds in the specified orientation */ 447 Rect getSearchBarBounds(int orientation) { 448 Rect bounds = new Rect(); 449 if (orientation == CellLayout.LANDSCAPE && 450 transposeLayoutWithOrientation) { 451 if (isLayoutRtl) { 452 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx, 453 availableWidthPx, availableHeightPx - edgeMarginPx); 454 } else { 455 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx, 456 availableHeightPx - edgeMarginPx); 457 } 458 } else { 459 if (isTablet()) { 460 // Pad the left and right of the workspace to ensure consistent spacing 461 // between all icons 462 int width = (orientation == CellLayout.LANDSCAPE) 463 ? Math.max(widthPx, heightPx) 464 : Math.min(widthPx, heightPx); 465 // XXX: If the icon size changes across orientations, we will have to take 466 // that into account here too. 467 int gap = (int) ((width - 2 * edgeMarginPx - 468 (numColumns * cellWidthPx)) / (2 * (numColumns + 1))); 469 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(), 470 availableWidthPx - (edgeMarginPx + gap), 471 searchBarSpaceHeightPx); 472 } else { 473 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left, 474 getSearchBarTopOffset(), 475 availableWidthPx - (desiredWorkspaceLeftRightMarginPx - 476 defaultWidgetPadding.right), searchBarSpaceHeightPx); 477 } 478 } 479 return bounds; 480 } 481 482 /** Returns the bounds of the workspace page indicators. */ 483 Rect getWorkspacePageIndicatorBounds(Rect insets) { 484 Rect workspacePadding = getWorkspacePadding(); 485 int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom; 486 return new Rect(workspacePadding.left, pageIndicatorTop, 487 widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx); 488 } 489 490 /** Returns the workspace padding in the specified orientation */ 491 Rect getWorkspacePadding() { 492 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 493 } 494 Rect getWorkspacePadding(int orientation) { 495 Rect searchBarBounds = getSearchBarBounds(orientation); 496 Rect padding = new Rect(); 497 if (orientation == CellLayout.LANDSCAPE && 498 transposeLayoutWithOrientation) { 499 // Pad the left and right of the workspace with search/hotseat bar sizes 500 if (isLayoutRtl) { 501 padding.set(hotseatBarHeightPx, edgeMarginPx, 502 searchBarBounds.width(), edgeMarginPx); 503 } else { 504 padding.set(searchBarBounds.width(), edgeMarginPx, 505 hotseatBarHeightPx, edgeMarginPx); 506 } 507 } else { 508 if (isTablet()) { 509 // Pad the left and right of the workspace to ensure consistent spacing 510 // between all icons 511 float gapScale = 1f + (dragViewScale - 1f) / 2f; 512 int width = (orientation == CellLayout.LANDSCAPE) 513 ? Math.max(widthPx, heightPx) 514 : Math.min(widthPx, heightPx); 515 int height = (orientation != CellLayout.LANDSCAPE) 516 ? Math.max(widthPx, heightPx) 517 : Math.min(widthPx, heightPx); 518 int paddingTop = searchBarBounds.bottom; 519 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx; 520 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) + 521 (numColumns * gapScale * cellWidthPx))); 522 int availableHeight = Math.max(0, height - paddingTop - paddingBottom 523 - (int) (2 * numRows * cellHeightPx)); 524 padding.set(availableWidth / 2, paddingTop + availableHeight / 2, 525 availableWidth / 2, paddingBottom + availableHeight / 2); 526 } else { 527 // Pad the top and bottom of the workspace with search/hotseat bar sizes 528 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left, 529 searchBarBounds.bottom, 530 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right, 531 hotseatBarHeightPx + pageIndicatorHeightPx); 532 } 533 } 534 return padding; 535 } 536 537 int getWorkspacePageSpacing(int orientation) { 538 if ((orientation == CellLayout.LANDSCAPE && 539 transposeLayoutWithOrientation) || isLargeTablet()) { 540 // In landscape mode the page spacing is set to the default. 541 return defaultPageSpacingPx; 542 } else { 543 // In portrait, we want the pages spaced such that there is no 544 // overhang of the previous / next page into the current page viewport. 545 // We assume symmetrical padding in portrait mode. 546 return 2 * getWorkspacePadding().left; 547 } 548 } 549 550 Rect getOverviewModeButtonBarRect() { 551 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx); 552 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx, 553 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight)); 554 return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx); 555 } 556 557 float getOverviewModeScale() { 558 Rect workspacePadding = getWorkspacePadding(); 559 Rect overviewBar = getOverviewModeButtonBarRect(); 560 int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom; 561 return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace; 562 } 563 564 // The rect returned will be extended to below the system ui that covers the workspace 565 Rect getHotseatRect() { 566 if (isVerticalBarLayout()) { 567 return new Rect(availableWidthPx - hotseatBarHeightPx, 0, 568 Integer.MAX_VALUE, availableHeightPx); 569 } else { 570 return new Rect(0, availableHeightPx - hotseatBarHeightPx, 571 availableWidthPx, Integer.MAX_VALUE); 572 } 573 } 574 575 int calculateCellWidth(int width, int countX) { 576 return width / countX; 577 } 578 int calculateCellHeight(int height, int countY) { 579 return height / countY; 580 } 581 582 boolean isPhone() { 583 return !isTablet && !isLargeTablet; 584 } 585 boolean isTablet() { 586 return isTablet; 587 } 588 boolean isLargeTablet() { 589 return isLargeTablet; 590 } 591 592 boolean isVerticalBarLayout() { 593 return isLandscape && transposeLayoutWithOrientation; 594 } 595 596 boolean shouldFadeAdjacentWorkspaceScreens() { 597 return isVerticalBarLayout() || isLargeTablet(); 598 } 599 600 int getVisibleChildCount(ViewGroup parent) { 601 int visibleChildren = 0; 602 for (int i = 0; i < parent.getChildCount(); i++) { 603 if (parent.getChildAt(i).getVisibility() != View.GONE) { 604 visibleChildren++; 605 } 606 } 607 return visibleChildren; 608 } 609 610 int calculateOverviewModeWidth(int visibleChildCount) { 611 return visibleChildCount * overviewModeBarItemWidthPx + 612 (visibleChildCount-1) * overviewModeBarSpacerWidthPx; 613 } 614 615 public void layout(Launcher launcher) { 616 FrameLayout.LayoutParams lp; 617 Resources res = launcher.getResources(); 618 boolean hasVerticalBarLayout = isVerticalBarLayout(); 619 620 // Layout the search bar space 621 View searchBar = launcher.getSearchBar(); 622 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams(); 623 if (hasVerticalBarLayout) { 624 // Vertical search bar space 625 lp.gravity = Gravity.TOP | Gravity.LEFT; 626 lp.width = searchBarSpaceHeightPx; 627 lp.height = LayoutParams.MATCH_PARENT; 628 searchBar.setPadding( 629 0, 2 * edgeMarginPx, 0, 630 2 * edgeMarginPx); 631 } else { 632 // Horizontal search bar space 633 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; 634 lp.width = searchBarSpaceWidthPx; 635 lp.height = searchBarSpaceHeightPx; 636 searchBar.setPadding( 637 2 * edgeMarginPx, 638 getSearchBarTopOffset(), 639 2 * edgeMarginPx, 0); 640 } 641 searchBar.setLayoutParams(lp); 642 643 // Layout the search bar 644 View qsbBar = launcher.getQsbBar(); 645 LayoutParams vglp = qsbBar.getLayoutParams(); 646 vglp.width = LayoutParams.MATCH_PARENT; 647 vglp.height = LayoutParams.MATCH_PARENT; 648 qsbBar.setLayoutParams(vglp); 649 650 // Layout the voice proxy 651 View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy); 652 if (voiceButtonProxy != null) { 653 if (hasVerticalBarLayout) { 654 // TODO: MOVE THIS INTO SEARCH BAR MEASURE 655 } else { 656 lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams(); 657 lp.gravity = Gravity.TOP | Gravity.END; 658 lp.width = (widthPx - searchBarSpaceWidthPx) / 2 + 659 2 * iconSizePx; 660 lp.height = searchBarSpaceHeightPx; 661 } 662 } 663 664 // Layout the workspace 665 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace); 666 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams(); 667 lp.gravity = Gravity.CENTER; 668 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT; 669 Rect padding = getWorkspacePadding(orientation); 670 workspace.setLayoutParams(lp); 671 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom); 672 workspace.setPageSpacing(getWorkspacePageSpacing(orientation)); 673 674 // Layout the hotseat 675 View hotseat = launcher.findViewById(R.id.hotseat); 676 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams(); 677 if (hasVerticalBarLayout) { 678 // Vertical hotseat 679 lp.gravity = Gravity.END; 680 lp.width = hotseatBarHeightPx; 681 lp.height = LayoutParams.MATCH_PARENT; 682 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx); 683 } else if (isTablet()) { 684 // Pad the hotseat with the workspace padding calculated above 685 lp.gravity = Gravity.BOTTOM; 686 lp.width = LayoutParams.MATCH_PARENT; 687 lp.height = hotseatBarHeightPx; 688 hotseat.setPadding(edgeMarginPx + padding.left, 0, 689 edgeMarginPx + padding.right, 690 2 * edgeMarginPx); 691 } else { 692 // For phones, layout the hotseat without any bottom margin 693 // to ensure that we have space for the folders 694 lp.gravity = Gravity.BOTTOM; 695 lp.width = LayoutParams.MATCH_PARENT; 696 lp.height = hotseatBarHeightPx; 697 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0, 698 2 * edgeMarginPx, 0); 699 } 700 hotseat.setLayoutParams(lp); 701 702 // Layout the page indicators 703 View pageIndicator = launcher.findViewById(R.id.page_indicator); 704 if (pageIndicator != null) { 705 if (hasVerticalBarLayout) { 706 // Hide the page indicators when we have vertical search/hotseat 707 pageIndicator.setVisibility(View.GONE); 708 } else { 709 // Put the page indicators above the hotseat 710 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams(); 711 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; 712 lp.width = LayoutParams.WRAP_CONTENT; 713 lp.height = LayoutParams.WRAP_CONTENT; 714 lp.bottomMargin = hotseatBarHeightPx; 715 pageIndicator.setLayoutParams(lp); 716 } 717 } 718 719 // Layout AllApps 720 AppsCustomizeTabHost host = (AppsCustomizeTabHost) 721 launcher.findViewById(R.id.apps_customize_pane); 722 if (host != null) { 723 // Center the all apps page indicator 724 int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f, 725 (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX))); 726 pageIndicator = host.findViewById(R.id.apps_customize_page_indicator); 727 if (pageIndicator != null) { 728 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams(); 729 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; 730 lp.width = LayoutParams.WRAP_CONTENT; 731 lp.height = pageIndicatorHeight; 732 pageIndicator.setLayoutParams(lp); 733 } 734 735 AppsCustomizePagedView pagedView = (AppsCustomizePagedView) 736 host.findViewById(R.id.apps_customize_pane_content); 737 padding = new Rect(); 738 if (pagedView != null) { 739 // Constrain the dimensions of all apps so that it does not span the full width 740 int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) / 741 (2 * (allAppsNumCols + 1)); 742 int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) / 743 (2 * (allAppsNumRows + 1)); 744 paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f)); 745 paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f)); 746 int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR)); 747 int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2; 748 // Only adjust the side paddings on landscape phones, or tablets 749 if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) { 750 padding.left = padding.right = gridPaddingLR; 751 } 752 // The icons are centered, so we can't just offset by the page indicator height 753 // because the empty space will actually be pageIndicatorHeight + paddingTB 754 padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB); 755 pagedView.setAllAppsPadding(padding); 756 pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight); 757 } 758 } 759 760 // Layout the Overview Mode 761 ViewGroup overviewMode = launcher.getOverviewPanel(); 762 if (overviewMode != null) { 763 Rect r = getOverviewModeButtonBarRect(); 764 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams(); 765 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; 766 lp.width = Math.min(availableWidthPx, 767 calculateOverviewModeWidth(getVisibleChildCount(overviewMode))); 768 lp.height = r.height(); 769 overviewMode.setLayoutParams(lp); 770 } 771 } 772} 773