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