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