blob: 8af2a7fbb203d15a6eca94e241151aa06eae79e3 [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 {
Winson Chungbe876472014-05-14 14:15:20 -070046 DeviceProfile profile;
Winson Chungb3800242013-10-24 11:01:54 -070047 float widthDps;
48 float heightDps;
49 float value;
50 PointF dimens;
51
Winson Chungbe876472014-05-14 14:15:20 -070052 DeviceProfileQuery(DeviceProfile p, float v) {
53 widthDps = p.minWidthDps;
54 heightDps = p.minHeightDps;
Winson Chungb3800242013-10-24 11:01:54 -070055 value = v;
Winson Chungbe876472014-05-14 14:15:20 -070056 dimens = new PointF(widthDps, heightDps);
57 profile = p;
Winson Chungb3800242013-10-24 11:01:54 -070058 }
59}
60
61public class DeviceProfile {
62 public static interface DeviceProfileCallbacks {
63 public void onAvailableSizeChanged(DeviceProfile grid);
64 }
65
66 String name;
67 float minWidthDps;
68 float minHeightDps;
69 float numRows;
70 float numColumns;
71 float numHotseatIcons;
72 private float iconSize;
73 private float iconTextSize;
74 private int iconDrawablePaddingOriginalPx;
75 private float hotseatIconSize;
76
Winson Chungbe876472014-05-14 14:15:20 -070077 int defaultLayoutId;
78 int defaultNoAllAppsLayoutId;
79
Winson Chungb3800242013-10-24 11:01:54 -070080 boolean isLandscape;
81 boolean isTablet;
82 boolean isLargeTablet;
Winson Chung42b3c062013-12-04 12:09:59 -080083 boolean isLayoutRtl;
Winson Chungb3800242013-10-24 11:01:54 -070084 boolean transposeLayoutWithOrientation;
85
86 int desiredWorkspaceLeftRightMarginPx;
87 int edgeMarginPx;
88 Rect defaultWidgetPadding;
89
90 int widthPx;
91 int heightPx;
92 int availableWidthPx;
93 int availableHeightPx;
94 int defaultPageSpacingPx;
95
96 int overviewModeMinIconZoneHeightPx;
97 int overviewModeMaxIconZoneHeightPx;
Jorim Jaggid017f882014-01-14 17:08:48 -080098 int overviewModeBarItemWidthPx;
99 int overviewModeBarSpacerWidthPx;
Winson Chungb3800242013-10-24 11:01:54 -0700100 float overviewModeIconZoneRatio;
101 float overviewModeScaleFactor;
102
103 int iconSizePx;
104 int iconTextSizePx;
105 int iconDrawablePaddingPx;
106 int cellWidthPx;
107 int cellHeightPx;
108 int allAppsIconSizePx;
109 int allAppsIconTextSizePx;
110 int allAppsCellWidthPx;
111 int allAppsCellHeightPx;
112 int allAppsCellPaddingPx;
113 int folderBackgroundOffset;
114 int folderIconSizePx;
115 int folderCellWidthPx;
116 int folderCellHeightPx;
117 int hotseatCellWidthPx;
118 int hotseatCellHeightPx;
119 int hotseatIconSizePx;
120 int hotseatBarHeightPx;
121 int hotseatAllAppsRank;
122 int allAppsNumRows;
123 int allAppsNumCols;
124 int searchBarSpaceWidthPx;
125 int searchBarSpaceMaxWidthPx;
126 int searchBarSpaceHeightPx;
127 int searchBarHeightPx;
128 int pageIndicatorHeightPx;
Adam Cohen63f1ec02014-08-12 09:23:13 -0700129 int allAppsButtonVisualSize;
Winson Chungb3800242013-10-24 11:01:54 -0700130
Winson Chung59a488a2013-12-10 12:32:14 -0800131 float dragViewScale;
132
Winson Chungb3800242013-10-24 11:01:54 -0700133 private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
134
135 DeviceProfile(String n, float w, float h, float r, float c,
Winson Chungbe876472014-05-14 14:15:20 -0700136 float is, float its, float hs, float his, int dlId, int dnalId) {
Winson Chungb3800242013-10-24 11:01:54 -0700137 // Ensure that we have an odd number of hotseat items (since we need to place all apps)
Nilesh Agrawal16f3ea82014-01-09 17:14:01 -0800138 if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) {
Winson Chungb3800242013-10-24 11:01:54 -0700139 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
140 }
141
142 name = n;
143 minWidthDps = w;
144 minHeightDps = h;
145 numRows = r;
146 numColumns = c;
147 iconSize = is;
148 iconTextSize = its;
149 numHotseatIcons = hs;
150 hotseatIconSize = his;
Winson Chungbe876472014-05-14 14:15:20 -0700151 defaultLayoutId = dlId;
152 defaultNoAllAppsLayoutId = dnalId;
Winson Chungb3800242013-10-24 11:01:54 -0700153 }
154
155 DeviceProfile(Context context,
156 ArrayList<DeviceProfile> profiles,
157 float minWidth, float minHeight,
158 int wPx, int hPx,
159 int awPx, int ahPx,
160 Resources res) {
161 DisplayMetrics dm = res.getDisplayMetrics();
162 ArrayList<DeviceProfileQuery> points =
163 new ArrayList<DeviceProfileQuery>();
164 transposeLayoutWithOrientation =
165 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
166 minWidthDps = minWidth;
167 minHeightDps = minHeight;
168
169 ComponentName cn = new ComponentName(context.getPackageName(),
170 this.getClass().getName());
171 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
172 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
173 desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
174 pageIndicatorHeightPx =
175 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
176 defaultPageSpacingPx =
177 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
178 allAppsCellPaddingPx =
179 res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
180 overviewModeMinIconZoneHeightPx =
181 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
182 overviewModeMaxIconZoneHeightPx =
183 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
Jorim Jaggid017f882014-01-14 17:08:48 -0800184 overviewModeBarItemWidthPx =
185 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
186 overviewModeBarSpacerWidthPx =
187 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
Winson Chungb3800242013-10-24 11:01:54 -0700188 overviewModeIconZoneRatio =
189 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
190 overviewModeScaleFactor =
191 res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
192
Winson Chungbe876472014-05-14 14:15:20 -0700193 // Find the closes profile given the width/height
Winson Chungb3800242013-10-24 11:01:54 -0700194 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700195 points.add(new DeviceProfileQuery(p, 0f));
Winson Chungb3800242013-10-24 11:01:54 -0700196 }
Winson Chungbe876472014-05-14 14:15:20 -0700197 DeviceProfile closestProfile = findClosestDeviceProfile(minWidth, minHeight, points);
198
199 // Snap to the closest row count
200 numRows = closestProfile.numRows;
201
202 // Snap to the closest column count
203 numColumns = closestProfile.numColumns;
204
205 // Snap to the closest hotseat size
206 numHotseatIcons = closestProfile.numHotseatIcons;
Winson Chungb3800242013-10-24 11:01:54 -0700207 hotseatAllAppsRank = (int) (numHotseatIcons / 2);
208
Winson Chungbe876472014-05-14 14:15:20 -0700209 // Snap to the closest default layout id
210 defaultLayoutId = closestProfile.defaultLayoutId;
211
212 // Snap to the closest default no all-apps layout id
213 defaultNoAllAppsLayoutId = closestProfile.defaultNoAllAppsLayoutId;
214
Winson Chungb3800242013-10-24 11:01:54 -0700215 // Interpolate the icon size
216 points.clear();
217 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700218 points.add(new DeviceProfileQuery(p, p.iconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700219 }
220 iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
221 // AllApps uses the original non-scaled icon size
222 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
223
224 // Interpolate the icon text size
225 points.clear();
226 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700227 points.add(new DeviceProfileQuery(p, p.iconTextSize));
Winson Chungb3800242013-10-24 11:01:54 -0700228 }
229 iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
230 iconDrawablePaddingOriginalPx =
231 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
232 // AllApps uses the original non-scaled icon text size
233 allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
234
235 // Interpolate the hotseat icon size
236 points.clear();
237 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700238 points.add(new DeviceProfileQuery(p, p.hotseatIconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700239 }
240 // Hotseat
241 hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
242
243 // Calculate the remaining vars
244 updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
245 updateAvailableDimensions(context);
Adam Cohen63f1ec02014-08-12 09:23:13 -0700246 computeAllAppsButtonSize(context);
247 }
248
249 /**
250 * Determine the exact visual footprint of the all apps button, taking into account scaling
251 * and internal padding of the drawable.
252 */
253 private void computeAllAppsButtonSize(Context context) {
254 Resources res = context.getResources();
255 float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
256 LauncherAppState app = LauncherAppState.getInstance();
257 allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding));
Winson Chungb3800242013-10-24 11:01:54 -0700258 }
259
260 void addCallback(DeviceProfileCallbacks cb) {
261 mCallbacks.add(cb);
262 cb.onAvailableSizeChanged(this);
263 }
264 void removeCallback(DeviceProfileCallbacks cb) {
265 mCallbacks.remove(cb);
266 }
267
268 private int getDeviceOrientation(Context context) {
269 WindowManager windowManager = (WindowManager)
270 context.getSystemService(Context.WINDOW_SERVICE);
271 Resources resources = context.getResources();
272 DisplayMetrics dm = resources.getDisplayMetrics();
273 Configuration config = resources.getConfiguration();
274 int rotation = windowManager.getDefaultDisplay().getRotation();
275
276 boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
277 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
278 boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
279 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
280 if (isLandscape || isRotatedPortrait) {
281 return CellLayout.LANDSCAPE;
282 } else {
283 return CellLayout.PORTRAIT;
284 }
285 }
286
287 private void updateAvailableDimensions(Context context) {
288 WindowManager windowManager = (WindowManager)
289 context.getSystemService(Context.WINDOW_SERVICE);
290 Display display = windowManager.getDefaultDisplay();
291 Resources resources = context.getResources();
292 DisplayMetrics dm = resources.getDisplayMetrics();
293 Configuration config = resources.getConfiguration();
294
295 // There are three possible configurations that the dynamic grid accounts for, portrait,
296 // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
297 // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
298 // the height is the smallest height (either with the nav bar at the bottom or to the
299 // side) and otherwise, the height is simply the largest possible height for a portrait
300 // device.
301 Point size = new Point();
302 Point smallestSize = new Point();
303 Point largestSize = new Point();
304 display.getSize(size);
305 display.getCurrentSizeRange(smallestSize, largestSize);
306 availableWidthPx = size.x;
307 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
308 availableHeightPx = smallestSize.y;
309 } else {
310 availableHeightPx = largestSize.y;
311 }
312
313 // Check to see if the icons fit in the new available height. If not, then we need to
314 // shrink the icon size.
Winson Chungb3800242013-10-24 11:01:54 -0700315 float scale = 1f;
316 int drawablePadding = iconDrawablePaddingOriginalPx;
317 updateIconSize(1f, drawablePadding, resources, dm);
318 float usedHeight = (cellHeightPx * numRows);
Winson Chung59a488a2013-12-10 12:32:14 -0800319
320 Rect workspacePadding = getWorkspacePadding();
Winson Chungb3800242013-10-24 11:01:54 -0700321 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
322 if (usedHeight > maxHeight) {
323 scale = maxHeight / usedHeight;
324 drawablePadding = 0;
325 }
326 updateIconSize(scale, drawablePadding, resources, dm);
327
328 // Make the callbacks
329 for (DeviceProfileCallbacks cb : mCallbacks) {
330 cb.onAvailableSizeChanged(this);
331 }
332 }
333
334 private void updateIconSize(float scale, int drawablePadding, Resources resources,
335 DisplayMetrics dm) {
336 iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
337 iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
338 iconDrawablePaddingPx = drawablePadding;
339 hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
340
341 // Search Bar
342 searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
343 searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
344 searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
Winson Chung69e04ea2013-12-02 14:43:44 -0800345 searchBarSpaceHeightPx = searchBarHeightPx + getSearchBarTopOffset();
Winson Chungb3800242013-10-24 11:01:54 -0700346
347 // Calculate the actual text height
348 Paint textPaint = new Paint();
349 textPaint.setTextSize(iconTextSizePx);
350 FontMetrics fm = textPaint.getFontMetrics();
351 cellWidthPx = iconSizePx;
352 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
Winson Chung59a488a2013-12-10 12:32:14 -0800353 final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale);
354 dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
Winson Chungb3800242013-10-24 11:01:54 -0700355
356 // Hotseat
357 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
358 hotseatCellWidthPx = iconSizePx;
359 hotseatCellHeightPx = iconSizePx;
360
361 // Folder
362 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
363 folderCellHeightPx = cellHeightPx + edgeMarginPx;
364 folderBackgroundOffset = -edgeMarginPx;
365 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
366
367 // All Apps
368 Rect padding = getWorkspacePadding(isLandscape ?
369 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
370 int pageIndicatorOffset =
371 resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
372 allAppsCellWidthPx = allAppsIconSizePx;
373 allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
374 int maxLongEdgeCellCount =
375 resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
376 int maxShortEdgeCellCount =
377 resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
378 int minEdgeCellCount =
379 resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
380 int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
381 int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
382
383 allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
384 (allAppsCellHeightPx + allAppsCellPaddingPx);
385 allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
386 allAppsNumCols = (availableWidthPx) /
387 (allAppsCellWidthPx + allAppsCellPaddingPx);
388 allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
389 }
390
391 void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
392 int awPx, int ahPx) {
Winson Chung42b3c062013-12-04 12:09:59 -0800393 Configuration configuration = resources.getConfiguration();
394 isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
Winson Chungb3800242013-10-24 11:01:54 -0700395 isTablet = resources.getBoolean(R.bool.is_tablet);
396 isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
Winson Chung6033ceb2014-02-05 12:37:42 -0800397 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
398 isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
399 } else {
400 isLayoutRtl = false;
401 }
Winson Chungb3800242013-10-24 11:01:54 -0700402 widthPx = wPx;
403 heightPx = hPx;
404 availableWidthPx = awPx;
405 availableHeightPx = ahPx;
406
407 updateAvailableDimensions(context);
408 }
409
410 private float dist(PointF p0, PointF p1) {
411 return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
412 (p1.y-p0.y)*(p1.y-p0.y));
413 }
414
415 private float weight(PointF a, PointF b,
416 float pow) {
417 float d = dist(a, b);
418 if (d == 0f) {
419 return Float.POSITIVE_INFINITY;
420 }
421 return (float) (1f / Math.pow(d, pow));
422 }
423
Winson Chungbe876472014-05-14 14:15:20 -0700424 /** Returns the closest device profile given the width and height and a list of profiles */
425 private DeviceProfile findClosestDeviceProfile(float width, float height,
426 ArrayList<DeviceProfileQuery> points) {
427 return findClosestDeviceProfiles(width, height, points).get(0).profile;
428 }
429
430 /** Returns the closest device profiles ordered by closeness to the specified width and height */
431 private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height,
432 ArrayList<DeviceProfileQuery> points) {
433 final PointF xy = new PointF(width, height);
434
435 // Sort the profiles by their closeness to the dimensions
436 ArrayList<DeviceProfileQuery> pointsByNearness = points;
437 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
438 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
439 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
440 }
441 });
442
443 return pointsByNearness;
444 }
445
Winson Chungb3800242013-10-24 11:01:54 -0700446 private float invDistWeightedInterpolate(float width, float height,
447 ArrayList<DeviceProfileQuery> points) {
448 float sum = 0;
449 float weights = 0;
450 float pow = 5;
451 float kNearestNeighbors = 3;
452 final PointF xy = new PointF(width, height);
453
Winson Chungbe876472014-05-14 14:15:20 -0700454 ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height,
455 points);
Winson Chungb3800242013-10-24 11:01:54 -0700456
457 for (int i = 0; i < pointsByNearness.size(); ++i) {
458 DeviceProfileQuery p = pointsByNearness.get(i);
459 if (i < kNearestNeighbors) {
460 float w = weight(xy, p.dimens, pow);
461 if (w == Float.POSITIVE_INFINITY) {
462 return p.value;
463 }
464 weights += w;
465 }
466 }
467
468 for (int i = 0; i < pointsByNearness.size(); ++i) {
469 DeviceProfileQuery p = pointsByNearness.get(i);
470 if (i < kNearestNeighbors) {
471 float w = weight(xy, p.dimens, pow);
472 sum += w * p.value / weights;
473 }
474 }
475
476 return sum;
477 }
478
Winson Chung69e04ea2013-12-02 14:43:44 -0800479 /** Returns the search bar top offset */
480 int getSearchBarTopOffset() {
481 if (isTablet() && !isVerticalBarLayout()) {
482 return 4 * edgeMarginPx;
483 } else {
484 return 2 * edgeMarginPx;
485 }
486 }
487
Winson Chungb3800242013-10-24 11:01:54 -0700488 /** Returns the search bar bounds in the current orientation */
489 Rect getSearchBarBounds() {
490 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
491 }
492 /** Returns the search bar bounds in the specified orientation */
493 Rect getSearchBarBounds(int orientation) {
494 Rect bounds = new Rect();
495 if (orientation == CellLayout.LANDSCAPE &&
496 transposeLayoutWithOrientation) {
Winson Chung42b3c062013-12-04 12:09:59 -0800497 if (isLayoutRtl) {
498 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
499 availableWidthPx, availableHeightPx - edgeMarginPx);
500 } else {
501 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
502 availableHeightPx - edgeMarginPx);
503 }
Winson Chungb3800242013-10-24 11:01:54 -0700504 } else {
505 if (isTablet()) {
506 // Pad the left and right of the workspace to ensure consistent spacing
507 // between all icons
508 int width = (orientation == CellLayout.LANDSCAPE)
509 ? Math.max(widthPx, heightPx)
510 : Math.min(widthPx, heightPx);
511 // XXX: If the icon size changes across orientations, we will have to take
512 // that into account here too.
513 int gap = (int) ((width - 2 * edgeMarginPx -
514 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
Winson Chung2cb24712013-12-02 15:00:39 -0800515 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
516 availableWidthPx - (edgeMarginPx + gap),
Winson Chungb3800242013-10-24 11:01:54 -0700517 searchBarSpaceHeightPx);
518 } else {
Winson Chung2cb24712013-12-02 15:00:39 -0800519 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
520 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700521 availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
522 defaultWidgetPadding.right), searchBarSpaceHeightPx);
523 }
524 }
525 return bounds;
526 }
527
Winson Chunga6945242014-01-08 14:04:34 -0800528 /** Returns the bounds of the workspace page indicators. */
529 Rect getWorkspacePageIndicatorBounds(Rect insets) {
530 Rect workspacePadding = getWorkspacePadding();
Winson Chung205cd772014-01-15 14:31:59 -0800531 if (isLandscape && transposeLayoutWithOrientation) {
532 if (isLayoutRtl) {
533 return new Rect(workspacePadding.left, workspacePadding.top,
534 workspacePadding.left + pageIndicatorHeightPx,
535 heightPx - workspacePadding.bottom - insets.bottom);
536 } else {
537 int pageIndicatorLeft = widthPx - workspacePadding.right;
538 return new Rect(pageIndicatorLeft, workspacePadding.top,
539 pageIndicatorLeft + pageIndicatorHeightPx,
540 heightPx - workspacePadding.bottom - insets.bottom);
541 }
542 } else {
543 int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom;
544 return new Rect(workspacePadding.left, pageIndicatorTop,
545 widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx);
546 }
Winson Chunga6945242014-01-08 14:04:34 -0800547 }
548
Winson Chungb3800242013-10-24 11:01:54 -0700549 /** Returns the workspace padding in the specified orientation */
550 Rect getWorkspacePadding() {
551 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
552 }
553 Rect getWorkspacePadding(int orientation) {
554 Rect searchBarBounds = getSearchBarBounds(orientation);
555 Rect padding = new Rect();
556 if (orientation == CellLayout.LANDSCAPE &&
557 transposeLayoutWithOrientation) {
558 // Pad the left and right of the workspace with search/hotseat bar sizes
Winson Chung42b3c062013-12-04 12:09:59 -0800559 if (isLayoutRtl) {
560 padding.set(hotseatBarHeightPx, edgeMarginPx,
561 searchBarBounds.width(), edgeMarginPx);
562 } else {
563 padding.set(searchBarBounds.width(), edgeMarginPx,
564 hotseatBarHeightPx, edgeMarginPx);
565 }
Winson Chungb3800242013-10-24 11:01:54 -0700566 } else {
567 if (isTablet()) {
568 // Pad the left and right of the workspace to ensure consistent spacing
569 // between all icons
Winson Chung59a488a2013-12-10 12:32:14 -0800570 float gapScale = 1f + (dragViewScale - 1f) / 2f;
Winson Chungb3800242013-10-24 11:01:54 -0700571 int width = (orientation == CellLayout.LANDSCAPE)
572 ? Math.max(widthPx, heightPx)
573 : Math.min(widthPx, heightPx);
Winson Chung59a488a2013-12-10 12:32:14 -0800574 int height = (orientation != CellLayout.LANDSCAPE)
575 ? Math.max(widthPx, heightPx)
576 : Math.min(widthPx, heightPx);
577 int paddingTop = searchBarBounds.bottom;
578 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
579 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
580 (numColumns * gapScale * cellWidthPx)));
581 int availableHeight = Math.max(0, height - paddingTop - paddingBottom
582 - (int) (2 * numRows * cellHeightPx));
583 padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
584 availableWidth / 2, paddingBottom + availableHeight / 2);
Winson Chungb3800242013-10-24 11:01:54 -0700585 } else {
586 // Pad the top and bottom of the workspace with search/hotseat bar sizes
587 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
588 searchBarBounds.bottom,
589 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
590 hotseatBarHeightPx + pageIndicatorHeightPx);
591 }
592 }
593 return padding;
594 }
595
596 int getWorkspacePageSpacing(int orientation) {
Winson Chung59a488a2013-12-10 12:32:14 -0800597 if ((orientation == CellLayout.LANDSCAPE &&
598 transposeLayoutWithOrientation) || isLargeTablet()) {
Winson Chungb3800242013-10-24 11:01:54 -0700599 // In landscape mode the page spacing is set to the default.
600 return defaultPageSpacingPx;
601 } else {
602 // In portrait, we want the pages spaced such that there is no
603 // overhang of the previous / next page into the current page viewport.
604 // We assume symmetrical padding in portrait mode.
Adam Cohenefb31e32014-01-16 16:07:50 -0800605 return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
Winson Chungb3800242013-10-24 11:01:54 -0700606 }
607 }
608
609 Rect getOverviewModeButtonBarRect() {
610 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
611 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
612 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
613 return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
614 }
615
616 float getOverviewModeScale() {
617 Rect workspacePadding = getWorkspacePadding();
618 Rect overviewBar = getOverviewModeButtonBarRect();
619 int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
620 return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
621 }
622
623 // The rect returned will be extended to below the system ui that covers the workspace
624 Rect getHotseatRect() {
625 if (isVerticalBarLayout()) {
626 return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
627 Integer.MAX_VALUE, availableHeightPx);
628 } else {
629 return new Rect(0, availableHeightPx - hotseatBarHeightPx,
630 availableWidthPx, Integer.MAX_VALUE);
631 }
632 }
633
634 int calculateCellWidth(int width, int countX) {
635 return width / countX;
636 }
637 int calculateCellHeight(int height, int countY) {
638 return height / countY;
639 }
640
641 boolean isPhone() {
642 return !isTablet && !isLargeTablet;
643 }
644 boolean isTablet() {
645 return isTablet;
646 }
647 boolean isLargeTablet() {
648 return isLargeTablet;
649 }
650
651 boolean isVerticalBarLayout() {
652 return isLandscape && transposeLayoutWithOrientation;
653 }
654
655 boolean shouldFadeAdjacentWorkspaceScreens() {
656 return isVerticalBarLayout() || isLargeTablet();
657 }
658
Jorim Jaggid017f882014-01-14 17:08:48 -0800659 int getVisibleChildCount(ViewGroup parent) {
660 int visibleChildren = 0;
661 for (int i = 0; i < parent.getChildCount(); i++) {
662 if (parent.getChildAt(i).getVisibility() != View.GONE) {
663 visibleChildren++;
664 }
665 }
666 return visibleChildren;
667 }
668
669 int calculateOverviewModeWidth(int visibleChildCount) {
670 return visibleChildCount * overviewModeBarItemWidthPx +
671 (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
672 }
673
Winson Chungb3800242013-10-24 11:01:54 -0700674 public void layout(Launcher launcher) {
675 FrameLayout.LayoutParams lp;
676 Resources res = launcher.getResources();
677 boolean hasVerticalBarLayout = isVerticalBarLayout();
678
679 // Layout the search bar space
680 View searchBar = launcher.getSearchBar();
681 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
682 if (hasVerticalBarLayout) {
Winson Chung69e04ea2013-12-02 14:43:44 -0800683 // Vertical search bar space
Winson Chungb3800242013-10-24 11:01:54 -0700684 lp.gravity = Gravity.TOP | Gravity.LEFT;
685 lp.width = searchBarSpaceHeightPx;
Adam Cohen24ce0b32014-01-14 16:18:14 -0800686 lp.height = LayoutParams.WRAP_CONTENT;
Winson Chungb3800242013-10-24 11:01:54 -0700687 searchBar.setPadding(
688 0, 2 * edgeMarginPx, 0,
689 2 * edgeMarginPx);
Adam Cohen24ce0b32014-01-14 16:18:14 -0800690
691 LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
692 targets.setOrientation(LinearLayout.VERTICAL);
Winson Chungb3800242013-10-24 11:01:54 -0700693 } else {
Winson Chung69e04ea2013-12-02 14:43:44 -0800694 // Horizontal search bar space
Winson Chungb3800242013-10-24 11:01:54 -0700695 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
696 lp.width = searchBarSpaceWidthPx;
697 lp.height = searchBarSpaceHeightPx;
698 searchBar.setPadding(
699 2 * edgeMarginPx,
Winson Chung69e04ea2013-12-02 14:43:44 -0800700 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700701 2 * edgeMarginPx, 0);
702 }
703 searchBar.setLayoutParams(lp);
704
Winson Chungb3800242013-10-24 11:01:54 -0700705 // Layout the voice proxy
706 View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
707 if (voiceButtonProxy != null) {
708 if (hasVerticalBarLayout) {
709 // TODO: MOVE THIS INTO SEARCH BAR MEASURE
710 } else {
711 lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
712 lp.gravity = Gravity.TOP | Gravity.END;
713 lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
714 2 * iconSizePx;
715 lp.height = searchBarSpaceHeightPx;
716 }
717 }
718
719 // Layout the workspace
720 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
721 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
722 lp.gravity = Gravity.CENTER;
723 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
724 Rect padding = getWorkspacePadding(orientation);
725 workspace.setLayoutParams(lp);
726 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
727 workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
728
729 // Layout the hotseat
730 View hotseat = launcher.findViewById(R.id.hotseat);
731 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
732 if (hasVerticalBarLayout) {
733 // Vertical hotseat
Winson Chung42b3c062013-12-04 12:09:59 -0800734 lp.gravity = Gravity.END;
Winson Chungb3800242013-10-24 11:01:54 -0700735 lp.width = hotseatBarHeightPx;
736 lp.height = LayoutParams.MATCH_PARENT;
737 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
738 } else if (isTablet()) {
Winson Chung59a488a2013-12-10 12:32:14 -0800739 // Pad the hotseat with the workspace padding calculated above
Winson Chungb3800242013-10-24 11:01:54 -0700740 lp.gravity = Gravity.BOTTOM;
741 lp.width = LayoutParams.MATCH_PARENT;
742 lp.height = hotseatBarHeightPx;
Winson Chung59a488a2013-12-10 12:32:14 -0800743 hotseat.setPadding(edgeMarginPx + padding.left, 0,
744 edgeMarginPx + padding.right,
Winson Chungb3800242013-10-24 11:01:54 -0700745 2 * edgeMarginPx);
746 } else {
747 // For phones, layout the hotseat without any bottom margin
748 // to ensure that we have space for the folders
749 lp.gravity = Gravity.BOTTOM;
750 lp.width = LayoutParams.MATCH_PARENT;
751 lp.height = hotseatBarHeightPx;
752 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
753 2 * edgeMarginPx, 0);
754 }
755 hotseat.setLayoutParams(lp);
756
757 // Layout the page indicators
758 View pageIndicator = launcher.findViewById(R.id.page_indicator);
759 if (pageIndicator != null) {
760 if (hasVerticalBarLayout) {
761 // Hide the page indicators when we have vertical search/hotseat
762 pageIndicator.setVisibility(View.GONE);
763 } else {
764 // Put the page indicators above the hotseat
765 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
766 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
767 lp.width = LayoutParams.WRAP_CONTENT;
768 lp.height = LayoutParams.WRAP_CONTENT;
769 lp.bottomMargin = hotseatBarHeightPx;
770 pageIndicator.setLayoutParams(lp);
771 }
772 }
773
774 // Layout AllApps
775 AppsCustomizeTabHost host = (AppsCustomizeTabHost)
776 launcher.findViewById(R.id.apps_customize_pane);
777 if (host != null) {
778 // Center the all apps page indicator
779 int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
780 (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
781 pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
782 if (pageIndicator != null) {
Adam Cohen96bb7982014-07-07 11:58:56 -0700783 LinearLayout.LayoutParams lllp = (LinearLayout.LayoutParams) pageIndicator.getLayoutParams();
784 lllp.width = LayoutParams.WRAP_CONTENT;
785 lllp.height = pageIndicatorHeight;
786 pageIndicator.setLayoutParams(lllp);
Winson Chungb3800242013-10-24 11:01:54 -0700787 }
788
789 AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
790 host.findViewById(R.id.apps_customize_pane_content);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700791
792 FrameLayout fakePageContainer = (FrameLayout)
793 host.findViewById(R.id.fake_page_container);
794 FrameLayout fakePage = (FrameLayout) host.findViewById(R.id.fake_page);
795
Winson Chungb3800242013-10-24 11:01:54 -0700796 padding = new Rect();
797 if (pagedView != null) {
798 // Constrain the dimensions of all apps so that it does not span the full width
799 int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
800 (2 * (allAppsNumCols + 1));
801 int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
802 (2 * (allAppsNumRows + 1));
803 paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
804 paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
805 int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
806 int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
Winson Chung495f44d2013-12-04 12:51:53 -0800807 // Only adjust the side paddings on landscape phones, or tablets
808 if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
Winson Chungb3800242013-10-24 11:01:54 -0700809 padding.left = padding.right = gridPaddingLR;
810 }
Adam Cohen9bfdb762014-07-21 17:44:06 -0700811
Winson Chungb3800242013-10-24 11:01:54 -0700812 // The icons are centered, so we can't just offset by the page indicator height
813 // because the empty space will actually be pageIndicatorHeight + paddingTB
814 padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700815
Winson Chungb3800242013-10-24 11:01:54 -0700816 pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700817 fakePage.setBackground(res.getDrawable(R.drawable.quantum_panel));
Adam Cohen96bb7982014-07-07 11:58:56 -0700818
819 // Horizontal padding for the whole paged view
Adam Cohen9bfdb762014-07-21 17:44:06 -0700820 int pagedFixedViewPadding =
Adam Cohen96bb7982014-07-07 11:58:56 -0700821 res.getDimensionPixelSize(R.dimen.apps_customize_horizontal_padding);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700822
823 padding.left += pagedFixedViewPadding;
824 padding.right += pagedFixedViewPadding;
825
826 pagedView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
827 fakePageContainer.setPadding(padding.left, padding.top, padding.right, padding.bottom);
828
Winson Chungb3800242013-10-24 11:01:54 -0700829 }
830 }
831
832 // Layout the Overview Mode
Jorim Jaggid017f882014-01-14 17:08:48 -0800833 ViewGroup overviewMode = launcher.getOverviewPanel();
Winson Chungb3800242013-10-24 11:01:54 -0700834 if (overviewMode != null) {
835 Rect r = getOverviewModeButtonBarRect();
836 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
837 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
Jorim Jaggid017f882014-01-14 17:08:48 -0800838 lp.width = Math.min(availableWidthPx,
839 calculateOverviewModeWidth(getVisibleChildCount(overviewMode)));
Winson Chungb3800242013-10-24 11:01:54 -0700840 lp.height = r.height();
841 overviewMode.setLayoutParams(lp);
842 }
843 }
844}