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