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