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 DeviceProfileQuery(float w, float h, float v)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 { onAvailableSizeChanged(DeviceProfile grid)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 DeviceProfile(String n, float w, float h, float r, float c, float is, float its, float hs, float his)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 DeviceProfile(Context context, ArrayList<DeviceProfile> profiles, float minWidth, float minHeight, int wPx, int hPx, int awPx, int ahPx, Resources res)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 addCallback(DeviceProfileCallbacks cb)237 void addCallback(DeviceProfileCallbacks cb) { 238 mCallbacks.add(cb); 239 cb.onAvailableSizeChanged(this); 240 } removeCallback(DeviceProfileCallbacks cb)241 void removeCallback(DeviceProfileCallbacks cb) { 242 mCallbacks.remove(cb); 243 } 244 getDeviceOrientation(Context context)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 updateAvailableDimensions(Context context)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 updateIconSize(float scale, int drawablePadding, Resources resources, DisplayMetrics dm)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 updateFromConfiguration(Context context, Resources resources, int wPx, int hPx, int awPx, int ahPx)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 dist(PointF p0, PointF p1)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 weight(PointF a, PointF b, float pow)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 invDistWeightedInterpolate(float width, float height, ArrayList<DeviceProfileQuery> points)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 */ getSearchBarTopOffset()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 */ getSearchBarBounds()448 Rect getSearchBarBounds() { 449 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 450 } 451 /** Returns the search bar bounds in the specified orientation */ getSearchBarBounds(int 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. */ getWorkspacePageIndicatorBounds(Rect insets)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 */ getWorkspacePadding()509 Rect getWorkspacePadding() { 510 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 511 } getWorkspacePadding(int orientation)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 getWorkspacePageSpacing(int orientation)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 getOverviewModeButtonBarRect()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 getOverviewModeScale()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 getHotseatRect()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 calculateCellWidth(int width, int countX)593 int calculateCellWidth(int width, int countX) { 594 return width / countX; 595 } calculateCellHeight(int height, int countY)596 int calculateCellHeight(int height, int countY) { 597 return height / countY; 598 } 599 isPhone()600 boolean isPhone() { 601 return !isTablet && !isLargeTablet; 602 } isTablet()603 boolean isTablet() { 604 return isTablet; 605 } isLargeTablet()606 boolean isLargeTablet() { 607 return isLargeTablet; 608 } 609 isVerticalBarLayout()610 boolean isVerticalBarLayout() { 611 return isLandscape && transposeLayoutWithOrientation; 612 } 613 shouldFadeAdjacentWorkspaceScreens()614 boolean shouldFadeAdjacentWorkspaceScreens() { 615 return isVerticalBarLayout() || isLargeTablet(); 616 } 617 getVisibleChildCount(ViewGroup parent)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 calculateOverviewModeWidth(int visibleChildCount)628 int calculateOverviewModeWidth(int visibleChildCount) { 629 return visibleChildCount * overviewModeBarItemWidthPx + 630 (visibleChildCount-1) * overviewModeBarSpacerWidthPx; 631 } 632 layout(Launcher launcher)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