blob: ec5e81f74e00961da50d39cff5e9583826d95e3f [file] [log] [blame]
Winson Chungb3800242013-10-24 11:01:54 -07001/*
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.LayoutParams;
35import android.view.WindowManager;
36import android.widget.FrameLayout;
37
38import java.util.ArrayList;
39import java.util.Collections;
40import java.util.Comparator;
41
42
43class DeviceProfileQuery {
44 float widthDps;
45 float heightDps;
46 float value;
47 PointF dimens;
48
49 DeviceProfileQuery(float w, float h, float v) {
50 widthDps = w;
51 heightDps = h;
52 value = v;
53 dimens = new PointF(w, h);
54 }
55}
56
57public class DeviceProfile {
58 public static interface DeviceProfileCallbacks {
59 public void onAvailableSizeChanged(DeviceProfile grid);
60 }
61
62 String name;
63 float minWidthDps;
64 float minHeightDps;
65 float numRows;
66 float numColumns;
67 float numHotseatIcons;
68 private float iconSize;
69 private float iconTextSize;
70 private int iconDrawablePaddingOriginalPx;
71 private float hotseatIconSize;
72
73 boolean isLandscape;
74 boolean isTablet;
75 boolean isLargeTablet;
76 boolean transposeLayoutWithOrientation;
77
78 int desiredWorkspaceLeftRightMarginPx;
79 int edgeMarginPx;
80 Rect defaultWidgetPadding;
81
82 int widthPx;
83 int heightPx;
84 int availableWidthPx;
85 int availableHeightPx;
86 int defaultPageSpacingPx;
87
88 int overviewModeMinIconZoneHeightPx;
89 int overviewModeMaxIconZoneHeightPx;
90 int overviewModeMaxBarWidthPx;
91 float overviewModeIconZoneRatio;
92 float overviewModeScaleFactor;
93
94 int iconSizePx;
95 int iconTextSizePx;
96 int iconDrawablePaddingPx;
97 int cellWidthPx;
98 int cellHeightPx;
99 int allAppsIconSizePx;
100 int allAppsIconTextSizePx;
101 int allAppsCellWidthPx;
102 int allAppsCellHeightPx;
103 int allAppsCellPaddingPx;
104 int folderBackgroundOffset;
105 int folderIconSizePx;
106 int folderCellWidthPx;
107 int folderCellHeightPx;
108 int hotseatCellWidthPx;
109 int hotseatCellHeightPx;
110 int hotseatIconSizePx;
111 int hotseatBarHeightPx;
112 int hotseatAllAppsRank;
113 int allAppsNumRows;
114 int allAppsNumCols;
115 int searchBarSpaceWidthPx;
116 int searchBarSpaceMaxWidthPx;
117 int searchBarSpaceHeightPx;
118 int searchBarHeightPx;
119 int pageIndicatorHeightPx;
120
121 private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
122
123 DeviceProfile(String n, float w, float h, float r, float c,
124 float is, float its, float hs, float his) {
125 // Ensure that we have an odd number of hotseat items (since we need to place all apps)
126 if (!AppsCustomizePagedView.DISABLE_ALL_APPS && hs % 2 == 0) {
127 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
128 }
129
130 name = n;
131 minWidthDps = w;
132 minHeightDps = h;
133 numRows = r;
134 numColumns = c;
135 iconSize = is;
136 iconTextSize = its;
137 numHotseatIcons = hs;
138 hotseatIconSize = his;
139 }
140
141 DeviceProfile(Context context,
142 ArrayList<DeviceProfile> profiles,
143 float minWidth, float minHeight,
144 int wPx, int hPx,
145 int awPx, int ahPx,
146 Resources res) {
147 DisplayMetrics dm = res.getDisplayMetrics();
148 ArrayList<DeviceProfileQuery> points =
149 new ArrayList<DeviceProfileQuery>();
150 transposeLayoutWithOrientation =
151 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
152 minWidthDps = minWidth;
153 minHeightDps = minHeight;
154
155 ComponentName cn = new ComponentName(context.getPackageName(),
156 this.getClass().getName());
157 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
158 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
159 desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
160 pageIndicatorHeightPx =
161 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
162 defaultPageSpacingPx =
163 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
164 allAppsCellPaddingPx =
165 res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
166 overviewModeMinIconZoneHeightPx =
167 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
168 overviewModeMaxIconZoneHeightPx =
169 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
170 overviewModeMaxBarWidthPx =
171 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_max_width);
172 overviewModeIconZoneRatio =
173 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
174 overviewModeScaleFactor =
175 res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
176
177 // Interpolate the rows
178 for (DeviceProfile p : profiles) {
179 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
180 }
181 numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
182 // Interpolate the columns
183 points.clear();
184 for (DeviceProfile p : profiles) {
185 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
186 }
187 numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
188 // Interpolate the hotseat length
189 points.clear();
190 for (DeviceProfile p : profiles) {
191 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
192 }
193 numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
194 hotseatAllAppsRank = (int) (numHotseatIcons / 2);
195
196 // Interpolate the icon size
197 points.clear();
198 for (DeviceProfile p : profiles) {
199 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
200 }
201 iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
202 // AllApps uses the original non-scaled icon size
203 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
204
205 // Interpolate the icon text size
206 points.clear();
207 for (DeviceProfile p : profiles) {
208 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
209 }
210 iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
211 iconDrawablePaddingOriginalPx =
212 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
213 // AllApps uses the original non-scaled icon text size
214 allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
215
216 // Interpolate the hotseat icon size
217 points.clear();
218 for (DeviceProfile p : profiles) {
219 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
220 }
221 // Hotseat
222 hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
223
224 // Calculate the remaining vars
225 updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
226 updateAvailableDimensions(context);
227 }
228
229 void addCallback(DeviceProfileCallbacks cb) {
230 mCallbacks.add(cb);
231 cb.onAvailableSizeChanged(this);
232 }
233 void removeCallback(DeviceProfileCallbacks cb) {
234 mCallbacks.remove(cb);
235 }
236
237 private int getDeviceOrientation(Context context) {
238 WindowManager windowManager = (WindowManager)
239 context.getSystemService(Context.WINDOW_SERVICE);
240 Resources resources = context.getResources();
241 DisplayMetrics dm = resources.getDisplayMetrics();
242 Configuration config = resources.getConfiguration();
243 int rotation = windowManager.getDefaultDisplay().getRotation();
244
245 boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
246 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
247 boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
248 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
249 if (isLandscape || isRotatedPortrait) {
250 return CellLayout.LANDSCAPE;
251 } else {
252 return CellLayout.PORTRAIT;
253 }
254 }
255
256 private void updateAvailableDimensions(Context context) {
257 WindowManager windowManager = (WindowManager)
258 context.getSystemService(Context.WINDOW_SERVICE);
259 Display display = windowManager.getDefaultDisplay();
260 Resources resources = context.getResources();
261 DisplayMetrics dm = resources.getDisplayMetrics();
262 Configuration config = resources.getConfiguration();
263
264 // There are three possible configurations that the dynamic grid accounts for, portrait,
265 // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
266 // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
267 // the height is the smallest height (either with the nav bar at the bottom or to the
268 // side) and otherwise, the height is simply the largest possible height for a portrait
269 // device.
270 Point size = new Point();
271 Point smallestSize = new Point();
272 Point largestSize = new Point();
273 display.getSize(size);
274 display.getCurrentSizeRange(smallestSize, largestSize);
275 availableWidthPx = size.x;
276 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
277 availableHeightPx = smallestSize.y;
278 } else {
279 availableHeightPx = largestSize.y;
280 }
281
282 // Check to see if the icons fit in the new available height. If not, then we need to
283 // shrink the icon size.
284 Rect workspacePadding = getWorkspacePadding();
285 float scale = 1f;
286 int drawablePadding = iconDrawablePaddingOriginalPx;
287 updateIconSize(1f, drawablePadding, resources, dm);
288 float usedHeight = (cellHeightPx * numRows);
289 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
290 if (usedHeight > maxHeight) {
291 scale = maxHeight / usedHeight;
292 drawablePadding = 0;
293 }
294 updateIconSize(scale, drawablePadding, resources, dm);
295
296 // Make the callbacks
297 for (DeviceProfileCallbacks cb : mCallbacks) {
298 cb.onAvailableSizeChanged(this);
299 }
300 }
301
302 private void updateIconSize(float scale, int drawablePadding, Resources resources,
303 DisplayMetrics dm) {
304 iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
305 iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
306 iconDrawablePaddingPx = drawablePadding;
307 hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
308
309 // Search Bar
310 searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
311 searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
312 searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
Winson Chung69e04ea2013-12-02 14:43:44 -0800313 searchBarSpaceHeightPx = searchBarHeightPx + getSearchBarTopOffset();
Winson Chungb3800242013-10-24 11:01:54 -0700314
315 // Calculate the actual text height
316 Paint textPaint = new Paint();
317 textPaint.setTextSize(iconTextSizePx);
318 FontMetrics fm = textPaint.getFontMetrics();
319 cellWidthPx = iconSizePx;
320 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
321
322 // Hotseat
323 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
324 hotseatCellWidthPx = iconSizePx;
325 hotseatCellHeightPx = iconSizePx;
326
327 // Folder
328 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
329 folderCellHeightPx = cellHeightPx + edgeMarginPx;
330 folderBackgroundOffset = -edgeMarginPx;
331 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
332
333 // All Apps
334 Rect padding = getWorkspacePadding(isLandscape ?
335 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
336 int pageIndicatorOffset =
337 resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
338 allAppsCellWidthPx = allAppsIconSizePx;
339 allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
340 int maxLongEdgeCellCount =
341 resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
342 int maxShortEdgeCellCount =
343 resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
344 int minEdgeCellCount =
345 resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
346 int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
347 int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
348
349 allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
350 (allAppsCellHeightPx + allAppsCellPaddingPx);
351 allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
352 allAppsNumCols = (availableWidthPx) /
353 (allAppsCellWidthPx + allAppsCellPaddingPx);
354 allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
355 }
356
357 void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
358 int awPx, int ahPx) {
359 isLandscape = (resources.getConfiguration().orientation ==
360 Configuration.ORIENTATION_LANDSCAPE);
361 isTablet = resources.getBoolean(R.bool.is_tablet);
362 isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
363 widthPx = wPx;
364 heightPx = hPx;
365 availableWidthPx = awPx;
366 availableHeightPx = ahPx;
367
368 updateAvailableDimensions(context);
369 }
370
371 private float dist(PointF p0, PointF p1) {
372 return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
373 (p1.y-p0.y)*(p1.y-p0.y));
374 }
375
376 private float weight(PointF a, PointF b,
377 float pow) {
378 float d = dist(a, b);
379 if (d == 0f) {
380 return Float.POSITIVE_INFINITY;
381 }
382 return (float) (1f / Math.pow(d, pow));
383 }
384
385 private float invDistWeightedInterpolate(float width, float height,
386 ArrayList<DeviceProfileQuery> points) {
387 float sum = 0;
388 float weights = 0;
389 float pow = 5;
390 float kNearestNeighbors = 3;
391 final PointF xy = new PointF(width, height);
392
393 ArrayList<DeviceProfileQuery> pointsByNearness = points;
394 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
395 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
396 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
397 }
398 });
399
400 for (int i = 0; i < pointsByNearness.size(); ++i) {
401 DeviceProfileQuery p = pointsByNearness.get(i);
402 if (i < kNearestNeighbors) {
403 float w = weight(xy, p.dimens, pow);
404 if (w == Float.POSITIVE_INFINITY) {
405 return p.value;
406 }
407 weights += w;
408 }
409 }
410
411 for (int i = 0; i < pointsByNearness.size(); ++i) {
412 DeviceProfileQuery p = pointsByNearness.get(i);
413 if (i < kNearestNeighbors) {
414 float w = weight(xy, p.dimens, pow);
415 sum += w * p.value / weights;
416 }
417 }
418
419 return sum;
420 }
421
Winson Chung69e04ea2013-12-02 14:43:44 -0800422 /** Returns the search bar top offset */
423 int getSearchBarTopOffset() {
424 if (isTablet() && !isVerticalBarLayout()) {
425 return 4 * edgeMarginPx;
426 } else {
427 return 2 * edgeMarginPx;
428 }
429 }
430
Winson Chungb3800242013-10-24 11:01:54 -0700431 /** Returns the search bar bounds in the current orientation */
432 Rect getSearchBarBounds() {
433 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
434 }
435 /** Returns the search bar bounds in the specified orientation */
436 Rect getSearchBarBounds(int orientation) {
437 Rect bounds = new Rect();
438 if (orientation == CellLayout.LANDSCAPE &&
439 transposeLayoutWithOrientation) {
440 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx, availableHeightPx - edgeMarginPx);
441 } else {
442 if (isTablet()) {
443 // Pad the left and right of the workspace to ensure consistent spacing
444 // between all icons
445 int width = (orientation == CellLayout.LANDSCAPE)
446 ? Math.max(widthPx, heightPx)
447 : Math.min(widthPx, heightPx);
448 // XXX: If the icon size changes across orientations, we will have to take
449 // that into account here too.
450 int gap = (int) ((width - 2 * edgeMarginPx -
451 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
Winson Chung2cb24712013-12-02 15:00:39 -0800452 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
453 availableWidthPx - (edgeMarginPx + gap),
Winson Chungb3800242013-10-24 11:01:54 -0700454 searchBarSpaceHeightPx);
455 } else {
Winson Chung2cb24712013-12-02 15:00:39 -0800456 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
457 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700458 availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
459 defaultWidgetPadding.right), searchBarSpaceHeightPx);
460 }
461 }
462 return bounds;
463 }
464
465 /** Returns the workspace padding in the specified orientation */
466 Rect getWorkspacePadding() {
467 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
468 }
469 Rect getWorkspacePadding(int orientation) {
470 Rect searchBarBounds = getSearchBarBounds(orientation);
471 Rect padding = new Rect();
472 if (orientation == CellLayout.LANDSCAPE &&
473 transposeLayoutWithOrientation) {
474 // Pad the left and right of the workspace with search/hotseat bar sizes
475 padding.set(searchBarBounds.right, edgeMarginPx,
476 hotseatBarHeightPx, edgeMarginPx);
477 } else {
478 if (isTablet()) {
479 // Pad the left and right of the workspace to ensure consistent spacing
480 // between all icons
481 int width = (orientation == CellLayout.LANDSCAPE)
482 ? Math.max(widthPx, heightPx)
483 : Math.min(widthPx, heightPx);
484 // XXX: If the icon size changes across orientations, we will have to take
485 // that into account here too.
486 int gap = (int) ((width - 2 * edgeMarginPx -
487 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
488 padding.set(edgeMarginPx + gap,
489 searchBarBounds.bottom,
490 edgeMarginPx + gap,
491 hotseatBarHeightPx + pageIndicatorHeightPx);
492 } else {
493 // Pad the top and bottom of the workspace with search/hotseat bar sizes
494 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
495 searchBarBounds.bottom,
496 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
497 hotseatBarHeightPx + pageIndicatorHeightPx);
498 }
499 }
500 return padding;
501 }
502
503 int getWorkspacePageSpacing(int orientation) {
504 if (orientation == CellLayout.LANDSCAPE &&
505 transposeLayoutWithOrientation) {
506 // In landscape mode the page spacing is set to the default.
507 return defaultPageSpacingPx;
508 } else {
509 // In portrait, we want the pages spaced such that there is no
510 // overhang of the previous / next page into the current page viewport.
511 // We assume symmetrical padding in portrait mode.
512 return 2 * getWorkspacePadding().left;
513 }
514 }
515
516 Rect getOverviewModeButtonBarRect() {
517 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
518 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
519 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
520 return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
521 }
522
523 float getOverviewModeScale() {
524 Rect workspacePadding = getWorkspacePadding();
525 Rect overviewBar = getOverviewModeButtonBarRect();
526 int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
527 return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
528 }
529
530 // The rect returned will be extended to below the system ui that covers the workspace
531 Rect getHotseatRect() {
532 if (isVerticalBarLayout()) {
533 return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
534 Integer.MAX_VALUE, availableHeightPx);
535 } else {
536 return new Rect(0, availableHeightPx - hotseatBarHeightPx,
537 availableWidthPx, Integer.MAX_VALUE);
538 }
539 }
540
541 int calculateCellWidth(int width, int countX) {
542 return width / countX;
543 }
544 int calculateCellHeight(int height, int countY) {
545 return height / countY;
546 }
547
548 boolean isPhone() {
549 return !isTablet && !isLargeTablet;
550 }
551 boolean isTablet() {
552 return isTablet;
553 }
554 boolean isLargeTablet() {
555 return isLargeTablet;
556 }
557
558 boolean isVerticalBarLayout() {
559 return isLandscape && transposeLayoutWithOrientation;
560 }
561
562 boolean shouldFadeAdjacentWorkspaceScreens() {
563 return isVerticalBarLayout() || isLargeTablet();
564 }
565
566 public void layout(Launcher launcher) {
567 FrameLayout.LayoutParams lp;
568 Resources res = launcher.getResources();
569 boolean hasVerticalBarLayout = isVerticalBarLayout();
570
571 // Layout the search bar space
572 View searchBar = launcher.getSearchBar();
573 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
574 if (hasVerticalBarLayout) {
Winson Chung69e04ea2013-12-02 14:43:44 -0800575 // Vertical search bar space
Winson Chungb3800242013-10-24 11:01:54 -0700576 lp.gravity = Gravity.TOP | Gravity.LEFT;
577 lp.width = searchBarSpaceHeightPx;
578 lp.height = LayoutParams.MATCH_PARENT;
579 searchBar.setPadding(
580 0, 2 * edgeMarginPx, 0,
581 2 * edgeMarginPx);
582 } else {
Winson Chung69e04ea2013-12-02 14:43:44 -0800583 // Horizontal search bar space
Winson Chungb3800242013-10-24 11:01:54 -0700584 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
585 lp.width = searchBarSpaceWidthPx;
586 lp.height = searchBarSpaceHeightPx;
587 searchBar.setPadding(
588 2 * edgeMarginPx,
Winson Chung69e04ea2013-12-02 14:43:44 -0800589 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700590 2 * edgeMarginPx, 0);
591 }
592 searchBar.setLayoutParams(lp);
593
594 // Layout the search bar
595 View qsbBar = launcher.getQsbBar();
596 LayoutParams vglp = qsbBar.getLayoutParams();
597 vglp.width = LayoutParams.MATCH_PARENT;
598 vglp.height = LayoutParams.MATCH_PARENT;
599 qsbBar.setLayoutParams(vglp);
600
601 // Layout the voice proxy
602 View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
603 if (voiceButtonProxy != null) {
604 if (hasVerticalBarLayout) {
605 // TODO: MOVE THIS INTO SEARCH BAR MEASURE
606 } else {
607 lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
608 lp.gravity = Gravity.TOP | Gravity.END;
609 lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
610 2 * iconSizePx;
611 lp.height = searchBarSpaceHeightPx;
612 }
613 }
614
615 // Layout the workspace
616 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
617 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
618 lp.gravity = Gravity.CENTER;
619 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
620 Rect padding = getWorkspacePadding(orientation);
621 workspace.setLayoutParams(lp);
622 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
623 workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
624
625 // Layout the hotseat
626 View hotseat = launcher.findViewById(R.id.hotseat);
627 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
628 if (hasVerticalBarLayout) {
629 // Vertical hotseat
630 lp.gravity = Gravity.RIGHT;
631 lp.width = hotseatBarHeightPx;
632 lp.height = LayoutParams.MATCH_PARENT;
633 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
634 } else if (isTablet()) {
635 // Pad the hotseat with the grid gap calculated above
636 int gridGap = (int) ((widthPx - 2 * edgeMarginPx -
637 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
638 int gridWidth = (int) ((numColumns * cellWidthPx) +
639 ((numColumns - 1) * gridGap));
640 int hotseatGap = (int) Math.max(0,
641 (gridWidth - (numHotseatIcons * hotseatCellWidthPx))
642 / (numHotseatIcons - 1));
643 lp.gravity = Gravity.BOTTOM;
644 lp.width = LayoutParams.MATCH_PARENT;
645 lp.height = hotseatBarHeightPx;
646 hotseat.setPadding(2 * edgeMarginPx + gridGap + hotseatGap, 0,
647 2 * edgeMarginPx + gridGap + hotseatGap,
648 2 * edgeMarginPx);
649 } else {
650 // For phones, layout the hotseat without any bottom margin
651 // to ensure that we have space for the folders
652 lp.gravity = Gravity.BOTTOM;
653 lp.width = LayoutParams.MATCH_PARENT;
654 lp.height = hotseatBarHeightPx;
655 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
656 2 * edgeMarginPx, 0);
657 }
658 hotseat.setLayoutParams(lp);
659
660 // Layout the page indicators
661 View pageIndicator = launcher.findViewById(R.id.page_indicator);
662 if (pageIndicator != null) {
663 if (hasVerticalBarLayout) {
664 // Hide the page indicators when we have vertical search/hotseat
665 pageIndicator.setVisibility(View.GONE);
666 } else {
667 // Put the page indicators above the hotseat
668 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
669 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
670 lp.width = LayoutParams.WRAP_CONTENT;
671 lp.height = LayoutParams.WRAP_CONTENT;
672 lp.bottomMargin = hotseatBarHeightPx;
673 pageIndicator.setLayoutParams(lp);
674 }
675 }
676
677 // Layout AllApps
678 AppsCustomizeTabHost host = (AppsCustomizeTabHost)
679 launcher.findViewById(R.id.apps_customize_pane);
680 if (host != null) {
681 // Center the all apps page indicator
682 int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
683 (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
684 pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
685 if (pageIndicator != null) {
686 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
687 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
688 lp.width = LayoutParams.WRAP_CONTENT;
689 lp.height = pageIndicatorHeight;
690 pageIndicator.setLayoutParams(lp);
691 }
692
693 AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
694 host.findViewById(R.id.apps_customize_pane_content);
695 padding = new Rect();
696 if (pagedView != null) {
697 // Constrain the dimensions of all apps so that it does not span the full width
698 int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
699 (2 * (allAppsNumCols + 1));
700 int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
701 (2 * (allAppsNumRows + 1));
702 paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
703 paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
704 int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
705 int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
Winson Chung495f44d2013-12-04 12:51:53 -0800706 // Only adjust the side paddings on landscape phones, or tablets
707 if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
Winson Chungb3800242013-10-24 11:01:54 -0700708 padding.left = padding.right = gridPaddingLR;
709 }
710 // The icons are centered, so we can't just offset by the page indicator height
711 // because the empty space will actually be pageIndicatorHeight + paddingTB
712 padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
713 pagedView.setAllAppsPadding(padding);
714 pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
715 }
716 }
717
718 // Layout the Overview Mode
719 View overviewMode = launcher.getOverviewPanel();
720 if (overviewMode != null) {
721 Rect r = getOverviewModeButtonBarRect();
722 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
723 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
724 lp.width = Math.min(availableWidthPx, overviewModeMaxBarWidthPx);
725 lp.height = r.height();
726 overviewMode.setLayoutParams(lp);
727 }
728 }
729}