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