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 DeviceProfileQuery(DeviceProfile p, float v)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 { onAvailableSizeChanged(DeviceProfile grid)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 DeviceProfile(String n, float w, float h, float r, float c, float is, float its, float hs, float his, int dlId, int dnalId)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 DeviceProfile()158 DeviceProfile() { 159 } 160 DeviceProfile(Context context, ArrayList<DeviceProfile> profiles, float minWidth, float minHeight, int wPx, int hPx, int awPx, int ahPx, Resources res)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 */ applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm)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 */ computeAllAppsButtonSize(Context context)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 addCallback(DeviceProfileCallbacks cb)297 void addCallback(DeviceProfileCallbacks cb) { 298 mCallbacks.add(cb); 299 cb.onAvailableSizeChanged(this); 300 } removeCallback(DeviceProfileCallbacks cb)301 void removeCallback(DeviceProfileCallbacks cb) { 302 mCallbacks.remove(cb); 303 } 304 getDeviceOrientation(Context context)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 updateAvailableDimensions(Context context)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 updateIconSize(float scale, int drawablePadding, Resources resources, DisplayMetrics dm)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 updateFromConfiguration(Context context, Resources resources, int wPx, int hPx, int awPx, int ahPx)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 dist(PointF p0, PointF p1)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 weight(PointF a, PointF b, float pow)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 */ findClosestDeviceProfile(float width, float height, ArrayList<DeviceProfileQuery> points)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 */ findClosestDeviceProfiles(float width, float height, ArrayList<DeviceProfileQuery> points)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 invDistWeightedInterpolate(float width, float height, ArrayList<DeviceProfileQuery> points)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 */ getSearchBarTopOffset()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 */ getSearchBarBounds()531 Rect getSearchBarBounds() { 532 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 533 } 534 /** Returns the search bar bounds in the specified orientation */ getSearchBarBounds(int 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. */ getWorkspacePageIndicatorBounds(Rect insets)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 */ getWorkspacePadding()592 Rect getWorkspacePadding() { 593 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 594 } getWorkspacePadding(int orientation)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 getWorkspacePageSpacing(int orientation)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 getOverviewModeButtonBarRect()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 getOverviewModeScale()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 getHotseatRect()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 calculateCellWidth(int width, int countX)676 int calculateCellWidth(int width, int countX) { 677 return width / countX; 678 } calculateCellHeight(int height, int countY)679 int calculateCellHeight(int height, int countY) { 680 return height / countY; 681 } 682 isPhone()683 boolean isPhone() { 684 return !isTablet && !isLargeTablet; 685 } isTablet()686 boolean isTablet() { 687 return isTablet; 688 } isLargeTablet()689 boolean isLargeTablet() { 690 return isLargeTablet; 691 } 692 isVerticalBarLayout()693 boolean isVerticalBarLayout() { 694 return isLandscape && transposeLayoutWithOrientation; 695 } 696 shouldFadeAdjacentWorkspaceScreens()697 boolean shouldFadeAdjacentWorkspaceScreens() { 698 return isVerticalBarLayout() || isLargeTablet(); 699 } 700 getVisibleChildCount(ViewGroup parent)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 calculateOverviewModeWidth(int visibleChildCount)711 int calculateOverviewModeWidth(int visibleChildCount) { 712 return visibleChildCount * overviewModeBarItemWidthPx + 713 (visibleChildCount-1) * overviewModeBarSpacerWidthPx; 714 } 715 layout(Launcher launcher)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