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