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