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