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