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