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