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 DeviceProfileQuery(DeviceProfile p, float v)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 { onAvailableSizeChanged(DeviceProfile grid)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 DeviceProfile(String n, float w, float h, float r, float c, float is, float its, float hs, float his, int dlId)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 DeviceProfile()155 DeviceProfile() { 156 } 157 DeviceProfile(Context context, ArrayList<DeviceProfile> profiles, float minWidth, float minHeight, int wPx, int hPx, int awPx, int ahPx, Resources res)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 */ applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm)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 */ computeAllAppsButtonSize(Context context)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 addCallback(DeviceProfileCallbacks cb)291 void addCallback(DeviceProfileCallbacks cb) { 292 mCallbacks.add(cb); 293 cb.onAvailableSizeChanged(this); 294 } removeCallback(DeviceProfileCallbacks cb)295 void removeCallback(DeviceProfileCallbacks cb) { 296 mCallbacks.remove(cb); 297 } 298 getDeviceOrientation(Context context)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 updateAvailableDimensions(Context context)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 updateIconSize(float scale, int drawablePadding, Resources resources, DisplayMetrics dm)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 updateFromConfiguration(Context context, Resources resources, int wPx, int hPx, int awPx, int ahPx)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 dist(PointF p0, PointF p1)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 weight(PointF a, PointF b, float pow)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 */ findClosestDeviceProfile(float width, float height, ArrayList<DeviceProfileQuery> points)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 */ findClosestDeviceProfiles(float width, float height, ArrayList<DeviceProfileQuery> points)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 invDistWeightedInterpolate(float width, float height, ArrayList<DeviceProfileQuery> points)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 */ getSearchBarTopOffset()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 */ getSearchBarBounds()521 Rect getSearchBarBounds() { 522 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 523 } 524 /** Returns the search bar bounds in the specified orientation */ getSearchBarBounds(int 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. */ getWorkspacePageIndicatorBounds(Rect insets)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 */ getWorkspacePadding()582 Rect getWorkspacePadding() { 583 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 584 } getWorkspacePadding(int orientation)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 getWorkspacePageSpacing(int orientation)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 getOverviewModeButtonBarRect()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 getOverviewModeScale()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 getHotseatRect()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 calculateCellWidth(int width, int countX)666 int calculateCellWidth(int width, int countX) { 667 return width / countX; 668 } calculateCellHeight(int height, int countY)669 int calculateCellHeight(int height, int countY) { 670 return height / countY; 671 } 672 isPhone()673 boolean isPhone() { 674 return !isTablet && !isLargeTablet; 675 } isTablet()676 boolean isTablet() { 677 return isTablet; 678 } isLargeTablet()679 boolean isLargeTablet() { 680 return isLargeTablet; 681 } 682 isVerticalBarLayout()683 boolean isVerticalBarLayout() { 684 return isLandscape && transposeLayoutWithOrientation; 685 } 686 shouldFadeAdjacentWorkspaceScreens()687 boolean shouldFadeAdjacentWorkspaceScreens() { 688 return isVerticalBarLayout() || isLargeTablet(); 689 } 690 getVisibleChildCount(ViewGroup parent)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 layout(Launcher launcher)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