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 17 package com.android.launcher3; 18 19 import android.appwidget.AppWidgetHostView; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.res.Configuration; 23 import android.content.res.Resources; 24 import android.graphics.Paint; 25 import android.graphics.Paint.FontMetrics; 26 import android.graphics.Point; 27 import android.graphics.PointF; 28 import android.graphics.Rect; 29 import android.util.DisplayMetrics; 30 import android.view.Display; 31 import android.view.Gravity; 32 import android.view.Surface; 33 import android.view.View; 34 import android.view.ViewGroup; 35 import android.view.ViewGroup.LayoutParams; 36 import android.view.WindowManager; 37 import android.widget.FrameLayout; 38 import android.widget.LinearLayout; 39 40 import java.util.ArrayList; 41 import java.util.Collections; 42 import java.util.Comparator; 43 44 45 class DeviceProfileQuery { 46 DeviceProfile profile; 47 float widthDps; 48 float heightDps; 49 float value; 50 PointF dimens; 51 52 DeviceProfileQuery(DeviceProfile p, float v) { 53 widthDps = p.minWidthDps; 54 heightDps = p.minHeightDps; 55 value = v; 56 dimens = new PointF(widthDps, heightDps); 57 profile = p; 58 } 59 } 60 61 public class DeviceProfile { 62 public static interface DeviceProfileCallbacks { 63 public void onAvailableSizeChanged(DeviceProfile grid); 64 } 65 66 String name; 67 float minWidthDps; 68 float minHeightDps; 69 float numRows; 70 float numColumns; 71 float numHotseatIcons; 72 float iconSize; 73 private float iconTextSize; 74 private int iconDrawablePaddingOriginalPx; 75 private float hotseatIconSize; 76 77 int defaultLayoutId; 78 int defaultNoAllAppsLayoutId; 79 80 boolean isLandscape; 81 boolean isTablet; 82 boolean isLargeTablet; 83 boolean isLayoutRtl; 84 boolean transposeLayoutWithOrientation; 85 86 int desiredWorkspaceLeftRightMarginPx; 87 int edgeMarginPx; 88 Rect defaultWidgetPadding; 89 90 int widthPx; 91 int heightPx; 92 int availableWidthPx; 93 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 int iconSizePx; 104 int iconTextSizePx; 105 int iconDrawablePaddingPx; 106 int cellWidthPx; 107 int cellHeightPx; 108 int allAppsIconSizePx; 109 int allAppsIconTextSizePx; 110 int allAppsCellWidthPx; 111 int allAppsCellHeightPx; 112 int allAppsCellPaddingPx; 113 int folderBackgroundOffset; 114 int folderIconSizePx; 115 int folderCellWidthPx; 116 int folderCellHeightPx; 117 int hotseatCellWidthPx; 118 int hotseatCellHeightPx; 119 int hotseatIconSizePx; 120 int hotseatBarHeightPx; 121 int hotseatAllAppsRank; 122 int allAppsNumRows; 123 int allAppsNumCols; 124 int searchBarSpaceWidthPx; 125 int searchBarSpaceMaxWidthPx; 126 int searchBarSpaceHeightPx; 127 int searchBarHeightPx; 128 int pageIndicatorHeightPx; 129 int allAppsButtonVisualSize; 130 131 float dragViewScale; 132 133 int allAppsShortEdgeCount = -1; 134 int allAppsLongEdgeCount = -1; 135 136 private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>(); 137 138 DeviceProfile(String n, float w, float h, float r, float c, 139 float is, float its, float hs, float his, int dlId, int dnalId) { 140 // Ensure that we have an odd number of hotseat items (since we need to place all apps) 141 if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) { 142 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces"); 143 } 144 145 name = n; 146 minWidthDps = w; 147 minHeightDps = h; 148 numRows = r; 149 numColumns = c; 150 iconSize = is; 151 iconTextSize = its; 152 numHotseatIcons = hs; 153 hotseatIconSize = his; 154 defaultLayoutId = dlId; 155 defaultNoAllAppsLayoutId = dnalId; 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 // Snap to the closest default no all-apps layout id 219 defaultNoAllAppsLayoutId = closestProfile.defaultNoAllAppsLayoutId; 220 221 // Interpolate the icon size 222 points.clear(); 223 for (DeviceProfile p : profiles) { 224 points.add(new DeviceProfileQuery(p, p.iconSize)); 225 } 226 iconSize = invDistWeightedInterpolate(minWidth, minHeight, points); 227 228 // AllApps uses the original non-scaled icon size 229 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm); 230 231 // Interpolate the icon text size 232 points.clear(); 233 for (DeviceProfile p : profiles) { 234 points.add(new DeviceProfileQuery(p, p.iconTextSize)); 235 } 236 iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points); 237 iconDrawablePaddingOriginalPx = 238 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding); 239 // AllApps uses the original non-scaled icon text size 240 allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm); 241 242 // Interpolate the hotseat icon size 243 points.clear(); 244 for (DeviceProfile p : profiles) { 245 points.add(new DeviceProfileQuery(p, p.hotseatIconSize)); 246 } 247 // Hotseat 248 hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points); 249 250 // If the partner customization apk contains any grid overrides, apply them 251 applyPartnerDeviceProfileOverrides(context, dm); 252 253 // Calculate the remaining vars 254 updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx); 255 updateAvailableDimensions(context); 256 computeAllAppsButtonSize(context); 257 } 258 259 /** 260 * Apply any Partner customization grid overrides. 261 * 262 * Currently we support: all apps row / column count. 263 */ 264 private void applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm) { 265 Partner p = Partner.get(ctx.getPackageManager()); 266 if (p != null) { 267 DeviceProfile partnerDp = p.getDeviceProfileOverride(dm); 268 if (partnerDp != null) { 269 if (partnerDp.numRows > 0 && partnerDp.numColumns > 0) { 270 numRows = partnerDp.numRows; 271 numColumns = partnerDp.numColumns; 272 } 273 if (partnerDp.allAppsShortEdgeCount > 0 && partnerDp.allAppsLongEdgeCount > 0) { 274 allAppsShortEdgeCount = partnerDp.allAppsShortEdgeCount; 275 allAppsLongEdgeCount = partnerDp.allAppsLongEdgeCount; 276 } 277 if (partnerDp.iconSize > 0) { 278 iconSize = partnerDp.iconSize; 279 // AllApps uses the original non-scaled icon size 280 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm); 281 } 282 } 283 } 284 } 285 286 /** 287 * Determine the exact visual footprint of the all apps button, taking into account scaling 288 * and internal padding of the drawable. 289 */ 290 private void computeAllAppsButtonSize(Context context) { 291 Resources res = context.getResources(); 292 float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f; 293 LauncherAppState app = LauncherAppState.getInstance(); 294 allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding)); 295 } 296 297 void addCallback(DeviceProfileCallbacks cb) { 298 mCallbacks.add(cb); 299 cb.onAvailableSizeChanged(this); 300 } 301 void removeCallback(DeviceProfileCallbacks cb) { 302 mCallbacks.remove(cb); 303 } 304 305 private int getDeviceOrientation(Context context) { 306 WindowManager windowManager = (WindowManager) 307 context.getSystemService(Context.WINDOW_SERVICE); 308 Resources resources = context.getResources(); 309 DisplayMetrics dm = resources.getDisplayMetrics(); 310 Configuration config = resources.getConfiguration(); 311 int rotation = windowManager.getDefaultDisplay().getRotation(); 312 313 boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) && 314 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180); 315 boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) && 316 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270); 317 if (isLandscape || isRotatedPortrait) { 318 return CellLayout.LANDSCAPE; 319 } else { 320 return CellLayout.PORTRAIT; 321 } 322 } 323 324 private void updateAvailableDimensions(Context context) { 325 WindowManager windowManager = (WindowManager) 326 context.getSystemService(Context.WINDOW_SERVICE); 327 Display display = windowManager.getDefaultDisplay(); 328 Resources resources = context.getResources(); 329 DisplayMetrics dm = resources.getDisplayMetrics(); 330 Configuration config = resources.getConfiguration(); 331 332 // There are three possible configurations that the dynamic grid accounts for, portrait, 333 // landscape with the nav bar at the bottom, and landscape with the nav bar at the side. 334 // To prevent waiting for fitSystemWindows(), we make the observation that in landscape, 335 // the height is the smallest height (either with the nav bar at the bottom or to the 336 // side) and otherwise, the height is simply the largest possible height for a portrait 337 // device. 338 Point size = new Point(); 339 Point smallestSize = new Point(); 340 Point largestSize = new Point(); 341 display.getSize(size); 342 display.getCurrentSizeRange(smallestSize, largestSize); 343 availableWidthPx = size.x; 344 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { 345 availableHeightPx = smallestSize.y; 346 } else { 347 availableHeightPx = largestSize.y; 348 } 349 350 // Check to see if the icons fit in the new available height. If not, then we need to 351 // shrink the icon size. 352 float scale = 1f; 353 int drawablePadding = iconDrawablePaddingOriginalPx; 354 updateIconSize(1f, drawablePadding, resources, dm); 355 float usedHeight = (cellHeightPx * numRows); 356 357 Rect workspacePadding = getWorkspacePadding(); 358 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom); 359 if (usedHeight > maxHeight) { 360 scale = maxHeight / usedHeight; 361 drawablePadding = 0; 362 } 363 updateIconSize(scale, drawablePadding, resources, dm); 364 365 // Make the callbacks 366 for (DeviceProfileCallbacks cb : mCallbacks) { 367 cb.onAvailableSizeChanged(this); 368 } 369 } 370 371 private void updateIconSize(float scale, int drawablePadding, Resources resources, 372 DisplayMetrics dm) { 373 iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale); 374 iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale); 375 iconDrawablePaddingPx = drawablePadding; 376 hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale); 377 378 // Search Bar 379 searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width); 380 searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height); 381 searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx); 382 searchBarSpaceHeightPx = searchBarHeightPx + getSearchBarTopOffset(); 383 384 // Calculate the actual text height 385 Paint textPaint = new Paint(); 386 textPaint.setTextSize(iconTextSizePx); 387 FontMetrics fm = textPaint.getFontMetrics(); 388 cellWidthPx = iconSizePx; 389 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top); 390 final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale); 391 dragViewScale = (iconSizePx + scaleDps) / iconSizePx; 392 393 // Hotseat 394 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx; 395 hotseatCellWidthPx = iconSizePx; 396 hotseatCellHeightPx = iconSizePx; 397 398 // Folder 399 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx; 400 folderCellHeightPx = cellHeightPx + edgeMarginPx; 401 folderBackgroundOffset = -edgeMarginPx; 402 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset; 403 404 // All Apps 405 Rect padding = getWorkspacePadding(isLandscape ? 406 CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 407 int pageIndicatorOffset = 408 resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset); 409 allAppsCellWidthPx = allAppsIconSizePx; 410 allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx; 411 int maxLongEdgeCellCount = 412 resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count); 413 int maxShortEdgeCellCount = 414 resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count); 415 int minEdgeCellCount = 416 resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count); 417 int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount); 418 int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount); 419 420 if (allAppsShortEdgeCount > 0 && allAppsLongEdgeCount > 0) { 421 allAppsNumRows = isLandscape ? allAppsShortEdgeCount : allAppsLongEdgeCount; 422 allAppsNumCols = isLandscape ? allAppsLongEdgeCount : allAppsShortEdgeCount; 423 } else { 424 allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) / 425 (allAppsCellHeightPx + allAppsCellPaddingPx); 426 allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows)); 427 allAppsNumCols = (availableWidthPx) / 428 (allAppsCellWidthPx + allAppsCellPaddingPx); 429 allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols)); 430 } 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 /** Returns the workspace padding in the specified orientation */ 592 Rect getWorkspacePadding() { 593 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 594 } 595 Rect getWorkspacePadding(int orientation) { 596 Rect searchBarBounds = getSearchBarBounds(orientation); 597 Rect padding = new Rect(); 598 if (orientation == CellLayout.LANDSCAPE && 599 transposeLayoutWithOrientation) { 600 // Pad the left and right of the workspace with search/hotseat bar sizes 601 if (isLayoutRtl) { 602 padding.set(hotseatBarHeightPx, edgeMarginPx, 603 searchBarBounds.width(), edgeMarginPx); 604 } else { 605 padding.set(searchBarBounds.width(), edgeMarginPx, 606 hotseatBarHeightPx, edgeMarginPx); 607 } 608 } else { 609 if (isTablet()) { 610 // Pad the left and right of the workspace to ensure consistent spacing 611 // between all icons 612 float gapScale = 1f + (dragViewScale - 1f) / 2f; 613 int width = (orientation == CellLayout.LANDSCAPE) 614 ? Math.max(widthPx, heightPx) 615 : Math.min(widthPx, heightPx); 616 int height = (orientation != CellLayout.LANDSCAPE) 617 ? Math.max(widthPx, heightPx) 618 : Math.min(widthPx, heightPx); 619 int paddingTop = searchBarBounds.bottom; 620 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx; 621 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) + 622 (numColumns * gapScale * cellWidthPx))); 623 int availableHeight = Math.max(0, height - paddingTop - paddingBottom 624 - (int) (2 * numRows * cellHeightPx)); 625 padding.set(availableWidth / 2, paddingTop + availableHeight / 2, 626 availableWidth / 2, paddingBottom + availableHeight / 2); 627 } else { 628 // Pad the top and bottom of the workspace with search/hotseat bar sizes 629 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left, 630 searchBarBounds.bottom, 631 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right, 632 hotseatBarHeightPx + pageIndicatorHeightPx); 633 } 634 } 635 return padding; 636 } 637 638 int getWorkspacePageSpacing(int orientation) { 639 if ((orientation == CellLayout.LANDSCAPE && 640 transposeLayoutWithOrientation) || isLargeTablet()) { 641 // In landscape mode the page spacing is set to the default. 642 return defaultPageSpacingPx; 643 } else { 644 // In portrait, we want the pages spaced such that there is no 645 // overhang of the previous / next page into the current page viewport. 646 // We assume symmetrical padding in portrait mode. 647 return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left); 648 } 649 } 650 651 Rect getOverviewModeButtonBarRect() { 652 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx); 653 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx, 654 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight)); 655 return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx); 656 } 657 658 float getOverviewModeScale() { 659 Rect workspacePadding = getWorkspacePadding(); 660 Rect overviewBar = getOverviewModeButtonBarRect(); 661 int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom; 662 return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace; 663 } 664 665 // The rect returned will be extended to below the system ui that covers the workspace 666 Rect getHotseatRect() { 667 if (isVerticalBarLayout()) { 668 return new Rect(availableWidthPx - hotseatBarHeightPx, 0, 669 Integer.MAX_VALUE, availableHeightPx); 670 } else { 671 return new Rect(0, availableHeightPx - hotseatBarHeightPx, 672 availableWidthPx, Integer.MAX_VALUE); 673 } 674 } 675 676 int calculateCellWidth(int width, int countX) { 677 return width / countX; 678 } 679 int calculateCellHeight(int height, int countY) { 680 return height / countY; 681 } 682 683 boolean isPhone() { 684 return !isTablet && !isLargeTablet; 685 } 686 boolean isTablet() { 687 return isTablet; 688 } 689 boolean isLargeTablet() { 690 return isLargeTablet; 691 } 692 693 boolean isVerticalBarLayout() { 694 return isLandscape && transposeLayoutWithOrientation; 695 } 696 697 boolean shouldFadeAdjacentWorkspaceScreens() { 698 return isVerticalBarLayout() || isLargeTablet(); 699 } 700 701 int getVisibleChildCount(ViewGroup parent) { 702 int visibleChildren = 0; 703 for (int i = 0; i < parent.getChildCount(); i++) { 704 if (parent.getChildAt(i).getVisibility() != View.GONE) { 705 visibleChildren++; 706 } 707 } 708 return visibleChildren; 709 } 710 711 int calculateOverviewModeWidth(int visibleChildCount) { 712 return visibleChildCount * overviewModeBarItemWidthPx + 713 (visibleChildCount-1) * overviewModeBarSpacerWidthPx; 714 } 715 716 public void layout(Launcher launcher) { 717 FrameLayout.LayoutParams lp; 718 Resources res = launcher.getResources(); 719 boolean hasVerticalBarLayout = isVerticalBarLayout(); 720 721 // Layout the search bar space 722 View searchBar = launcher.getSearchBar(); 723 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams(); 724 if (hasVerticalBarLayout) { 725 // Vertical search bar space 726 lp.gravity = Gravity.TOP | Gravity.LEFT; 727 lp.width = searchBarSpaceHeightPx; 728 lp.height = LayoutParams.WRAP_CONTENT; 729 searchBar.setPadding( 730 0, 2 * edgeMarginPx, 0, 731 2 * edgeMarginPx); 732 733 LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar); 734 targets.setOrientation(LinearLayout.VERTICAL); 735 } else { 736 // Horizontal search bar space 737 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; 738 lp.width = searchBarSpaceWidthPx; 739 lp.height = searchBarSpaceHeightPx; 740 searchBar.setPadding( 741 2 * edgeMarginPx, 742 getSearchBarTopOffset(), 743 2 * edgeMarginPx, 0); 744 } 745 searchBar.setLayoutParams(lp); 746 747 // Layout the voice proxy 748 View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy); 749 if (voiceButtonProxy != null) { 750 if (hasVerticalBarLayout) { 751 // TODO: MOVE THIS INTO SEARCH BAR MEASURE 752 } else { 753 lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams(); 754 lp.gravity = Gravity.TOP | Gravity.END; 755 lp.width = (widthPx - searchBarSpaceWidthPx) / 2 + 756 2 * iconSizePx; 757 lp.height = searchBarSpaceHeightPx; 758 } 759 } 760 761 // Layout the workspace 762 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace); 763 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams(); 764 lp.gravity = Gravity.CENTER; 765 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT; 766 Rect padding = getWorkspacePadding(orientation); 767 workspace.setLayoutParams(lp); 768 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom); 769 workspace.setPageSpacing(getWorkspacePageSpacing(orientation)); 770 771 // Layout the hotseat 772 View hotseat = launcher.findViewById(R.id.hotseat); 773 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams(); 774 if (hasVerticalBarLayout) { 775 // Vertical hotseat 776 lp.gravity = Gravity.END; 777 lp.width = hotseatBarHeightPx; 778 lp.height = LayoutParams.MATCH_PARENT; 779 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx); 780 } else if (isTablet()) { 781 // Pad the hotseat with the workspace padding calculated above 782 lp.gravity = Gravity.BOTTOM; 783 lp.width = LayoutParams.MATCH_PARENT; 784 lp.height = hotseatBarHeightPx; 785 hotseat.setPadding(edgeMarginPx + padding.left, 0, 786 edgeMarginPx + padding.right, 787 2 * edgeMarginPx); 788 } else { 789 // For phones, layout the hotseat without any bottom margin 790 // to ensure that we have space for the folders 791 lp.gravity = Gravity.BOTTOM; 792 lp.width = LayoutParams.MATCH_PARENT; 793 lp.height = hotseatBarHeightPx; 794 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0, 795 2 * edgeMarginPx, 0); 796 } 797 hotseat.setLayoutParams(lp); 798 799 // Layout the page indicators 800 View pageIndicator = launcher.findViewById(R.id.page_indicator); 801 if (pageIndicator != null) { 802 if (hasVerticalBarLayout) { 803 // Hide the page indicators when we have vertical search/hotseat 804 pageIndicator.setVisibility(View.GONE); 805 } else { 806 // Put the page indicators above the hotseat 807 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams(); 808 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; 809 lp.width = LayoutParams.WRAP_CONTENT; 810 lp.height = LayoutParams.WRAP_CONTENT; 811 lp.bottomMargin = hotseatBarHeightPx; 812 pageIndicator.setLayoutParams(lp); 813 } 814 } 815 816 // Layout AllApps 817 AppsCustomizeTabHost host = (AppsCustomizeTabHost) 818 launcher.findViewById(R.id.apps_customize_pane); 819 if (host != null) { 820 // Center the all apps page indicator 821 int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f, 822 (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX))); 823 pageIndicator = host.findViewById(R.id.apps_customize_page_indicator); 824 if (pageIndicator != null) { 825 LinearLayout.LayoutParams lllp = (LinearLayout.LayoutParams) pageIndicator.getLayoutParams(); 826 lllp.width = LayoutParams.WRAP_CONTENT; 827 lllp.height = pageIndicatorHeight; 828 pageIndicator.setLayoutParams(lllp); 829 } 830 831 AppsCustomizePagedView pagedView = (AppsCustomizePagedView) 832 host.findViewById(R.id.apps_customize_pane_content); 833 834 FrameLayout fakePageContainer = (FrameLayout) 835 host.findViewById(R.id.fake_page_container); 836 FrameLayout fakePage = (FrameLayout) host.findViewById(R.id.fake_page); 837 838 padding = new Rect(); 839 if (pagedView != null) { 840 // Constrain the dimensions of all apps so that it does not span the full width 841 int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) / 842 (2 * (allAppsNumCols + 1)); 843 int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) / 844 (2 * (allAppsNumRows + 1)); 845 paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f)); 846 paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f)); 847 int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR)); 848 int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2; 849 // Only adjust the side paddings on landscape phones, or tablets 850 if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) { 851 padding.left = padding.right = gridPaddingLR; 852 } 853 854 // The icons are centered, so we can't just offset by the page indicator height 855 // because the empty space will actually be pageIndicatorHeight + paddingTB 856 padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB); 857 858 pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight); 859 fakePage.setBackground(res.getDrawable(R.drawable.quantum_panel)); 860 861 // Horizontal padding for the whole paged view 862 int pagedFixedViewPadding = 863 res.getDimensionPixelSize(R.dimen.apps_customize_horizontal_padding); 864 865 padding.left += pagedFixedViewPadding; 866 padding.right += pagedFixedViewPadding; 867 868 pagedView.setPadding(padding.left, padding.top, padding.right, padding.bottom); 869 fakePageContainer.setPadding(padding.left, padding.top, padding.right, padding.bottom); 870 871 } 872 } 873 874 // Layout the Overview Mode 875 ViewGroup overviewMode = launcher.getOverviewPanel(); 876 if (overviewMode != null) { 877 Rect r = getOverviewModeButtonBarRect(); 878 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams(); 879 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; 880 lp.width = Math.min(availableWidthPx, 881 calculateOverviewModeWidth(getVisibleChildCount(overviewMode))); 882 lp.height = r.height(); 883 overviewMode.setLayoutParams(lp); 884 } 885 } 886 } 887