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