blob: daf5556d4273c22651d64bb4c757463b23089b59 [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;
Adam Cohen4ae96ce2014-08-29 15:05:48 -070072 float iconSize;
Winson Chungb3800242013-10-24 11:01:54 -070073 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
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700133 int allAppsShortEdgeCount = -1;
134 int allAppsLongEdgeCount = -1;
135
Winson Chungb3800242013-10-24 11:01:54 -0700136 private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
137
138 DeviceProfile(String n, float w, float h, float r, float c,
Winson Chungbe876472014-05-14 14:15:20 -0700139 float is, float its, float hs, float his, int dlId, int dnalId) {
Winson Chungb3800242013-10-24 11:01:54 -0700140 // Ensure that we have an odd number of hotseat items (since we need to place all apps)
Nilesh Agrawal16f3ea82014-01-09 17:14:01 -0800141 if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) {
Winson Chungb3800242013-10-24 11:01:54 -0700142 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
143 }
144
145 name = n;
146 minWidthDps = w;
147 minHeightDps = h;
148 numRows = r;
149 numColumns = c;
150 iconSize = is;
151 iconTextSize = its;
152 numHotseatIcons = hs;
153 hotseatIconSize = his;
Winson Chungbe876472014-05-14 14:15:20 -0700154 defaultLayoutId = dlId;
155 defaultNoAllAppsLayoutId = dnalId;
Winson Chungb3800242013-10-24 11:01:54 -0700156 }
157
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700158 DeviceProfile() {
159 }
160
Winson Chungb3800242013-10-24 11:01:54 -0700161 DeviceProfile(Context context,
162 ArrayList<DeviceProfile> profiles,
163 float minWidth, float minHeight,
164 int wPx, int hPx,
165 int awPx, int ahPx,
166 Resources res) {
167 DisplayMetrics dm = res.getDisplayMetrics();
168 ArrayList<DeviceProfileQuery> points =
169 new ArrayList<DeviceProfileQuery>();
170 transposeLayoutWithOrientation =
171 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
172 minWidthDps = minWidth;
173 minHeightDps = minHeight;
174
175 ComponentName cn = new ComponentName(context.getPackageName(),
176 this.getClass().getName());
177 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
178 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
179 desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
180 pageIndicatorHeightPx =
181 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
182 defaultPageSpacingPx =
183 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
184 allAppsCellPaddingPx =
185 res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
186 overviewModeMinIconZoneHeightPx =
187 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
188 overviewModeMaxIconZoneHeightPx =
189 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
Jorim Jaggid017f882014-01-14 17:08:48 -0800190 overviewModeBarItemWidthPx =
191 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
192 overviewModeBarSpacerWidthPx =
193 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
Winson Chungb3800242013-10-24 11:01:54 -0700194 overviewModeIconZoneRatio =
195 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
196 overviewModeScaleFactor =
197 res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
198
Winson Chungbe876472014-05-14 14:15:20 -0700199 // Find the closes profile given the width/height
Winson Chungb3800242013-10-24 11:01:54 -0700200 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700201 points.add(new DeviceProfileQuery(p, 0f));
Winson Chungb3800242013-10-24 11:01:54 -0700202 }
Winson Chungbe876472014-05-14 14:15:20 -0700203 DeviceProfile closestProfile = findClosestDeviceProfile(minWidth, minHeight, points);
204
205 // Snap to the closest row count
206 numRows = closestProfile.numRows;
207
208 // Snap to the closest column count
209 numColumns = closestProfile.numColumns;
210
211 // Snap to the closest hotseat size
212 numHotseatIcons = closestProfile.numHotseatIcons;
Winson Chungb3800242013-10-24 11:01:54 -0700213 hotseatAllAppsRank = (int) (numHotseatIcons / 2);
214
Winson Chungbe876472014-05-14 14:15:20 -0700215 // Snap to the closest default layout id
216 defaultLayoutId = closestProfile.defaultLayoutId;
217
218 // Snap to the closest default no all-apps layout id
219 defaultNoAllAppsLayoutId = closestProfile.defaultNoAllAppsLayoutId;
220
Winson Chungb3800242013-10-24 11:01:54 -0700221 // Interpolate the icon size
222 points.clear();
223 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700224 points.add(new DeviceProfileQuery(p, p.iconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700225 }
226 iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700227
Winson Chungb3800242013-10-24 11:01:54 -0700228 // AllApps uses the original non-scaled icon size
229 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
230
231 // Interpolate the icon text size
232 points.clear();
233 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700234 points.add(new DeviceProfileQuery(p, p.iconTextSize));
Winson Chungb3800242013-10-24 11:01:54 -0700235 }
236 iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
237 iconDrawablePaddingOriginalPx =
238 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
239 // AllApps uses the original non-scaled icon text size
240 allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
241
242 // Interpolate the hotseat icon size
243 points.clear();
244 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700245 points.add(new DeviceProfileQuery(p, p.hotseatIconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700246 }
247 // Hotseat
248 hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
249
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700250 // If the partner customization apk contains any grid overrides, apply them
251 applyPartnerDeviceProfileOverrides(context, dm);
252
Winson Chungb3800242013-10-24 11:01:54 -0700253 // Calculate the remaining vars
254 updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
255 updateAvailableDimensions(context);
Adam Cohen63f1ec02014-08-12 09:23:13 -0700256 computeAllAppsButtonSize(context);
257 }
258
259 /**
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700260 * Apply any Partner customization grid overrides.
261 *
262 * Currently we support: all apps row / column count.
263 */
264 private void applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm) {
265 Partner p = Partner.get(ctx.getPackageManager());
266 if (p != null) {
267 DeviceProfile partnerDp = p.getDeviceProfileOverride(dm);
268 if (partnerDp != null) {
269 if (partnerDp.numRows > 0 && partnerDp.numColumns > 0) {
270 numRows = partnerDp.numRows;
271 numColumns = partnerDp.numColumns;
272 }
273 if (partnerDp.allAppsShortEdgeCount > 0 && partnerDp.allAppsLongEdgeCount > 0) {
274 allAppsShortEdgeCount = partnerDp.allAppsShortEdgeCount;
275 allAppsLongEdgeCount = partnerDp.allAppsLongEdgeCount;
276 }
277 if (partnerDp.iconSize > 0) {
278 iconSize = partnerDp.iconSize;
279 // AllApps uses the original non-scaled icon size
280 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
281 }
282 }
283 }
284 }
285
286 /**
Adam Cohen63f1ec02014-08-12 09:23:13 -0700287 * Determine the exact visual footprint of the all apps button, taking into account scaling
288 * and internal padding of the drawable.
289 */
290 private void computeAllAppsButtonSize(Context context) {
291 Resources res = context.getResources();
292 float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
293 LauncherAppState app = LauncherAppState.getInstance();
294 allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding));
Winson Chungb3800242013-10-24 11:01:54 -0700295 }
296
297 void addCallback(DeviceProfileCallbacks cb) {
298 mCallbacks.add(cb);
299 cb.onAvailableSizeChanged(this);
300 }
301 void removeCallback(DeviceProfileCallbacks cb) {
302 mCallbacks.remove(cb);
303 }
304
305 private int getDeviceOrientation(Context context) {
306 WindowManager windowManager = (WindowManager)
307 context.getSystemService(Context.WINDOW_SERVICE);
308 Resources resources = context.getResources();
309 DisplayMetrics dm = resources.getDisplayMetrics();
310 Configuration config = resources.getConfiguration();
311 int rotation = windowManager.getDefaultDisplay().getRotation();
312
313 boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
314 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
315 boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
316 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
317 if (isLandscape || isRotatedPortrait) {
318 return CellLayout.LANDSCAPE;
319 } else {
320 return CellLayout.PORTRAIT;
321 }
322 }
323
324 private void updateAvailableDimensions(Context context) {
325 WindowManager windowManager = (WindowManager)
326 context.getSystemService(Context.WINDOW_SERVICE);
327 Display display = windowManager.getDefaultDisplay();
328 Resources resources = context.getResources();
329 DisplayMetrics dm = resources.getDisplayMetrics();
330 Configuration config = resources.getConfiguration();
331
332 // There are three possible configurations that the dynamic grid accounts for, portrait,
333 // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
334 // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
335 // the height is the smallest height (either with the nav bar at the bottom or to the
336 // side) and otherwise, the height is simply the largest possible height for a portrait
337 // device.
338 Point size = new Point();
339 Point smallestSize = new Point();
340 Point largestSize = new Point();
341 display.getSize(size);
342 display.getCurrentSizeRange(smallestSize, largestSize);
343 availableWidthPx = size.x;
344 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
345 availableHeightPx = smallestSize.y;
346 } else {
347 availableHeightPx = largestSize.y;
348 }
349
350 // Check to see if the icons fit in the new available height. If not, then we need to
351 // shrink the icon size.
Winson Chungb3800242013-10-24 11:01:54 -0700352 float scale = 1f;
353 int drawablePadding = iconDrawablePaddingOriginalPx;
354 updateIconSize(1f, drawablePadding, resources, dm);
355 float usedHeight = (cellHeightPx * numRows);
Winson Chung59a488a2013-12-10 12:32:14 -0800356
357 Rect workspacePadding = getWorkspacePadding();
Winson Chungb3800242013-10-24 11:01:54 -0700358 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
359 if (usedHeight > maxHeight) {
360 scale = maxHeight / usedHeight;
361 drawablePadding = 0;
362 }
363 updateIconSize(scale, drawablePadding, resources, dm);
364
365 // Make the callbacks
366 for (DeviceProfileCallbacks cb : mCallbacks) {
367 cb.onAvailableSizeChanged(this);
368 }
369 }
370
371 private void updateIconSize(float scale, int drawablePadding, Resources resources,
372 DisplayMetrics dm) {
373 iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
374 iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
375 iconDrawablePaddingPx = drawablePadding;
376 hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
377
378 // Search Bar
379 searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
380 searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
381 searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
Winson Chung69e04ea2013-12-02 14:43:44 -0800382 searchBarSpaceHeightPx = searchBarHeightPx + getSearchBarTopOffset();
Winson Chungb3800242013-10-24 11:01:54 -0700383
384 // Calculate the actual text height
385 Paint textPaint = new Paint();
386 textPaint.setTextSize(iconTextSizePx);
387 FontMetrics fm = textPaint.getFontMetrics();
388 cellWidthPx = iconSizePx;
389 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
Winson Chung59a488a2013-12-10 12:32:14 -0800390 final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale);
391 dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
Winson Chungb3800242013-10-24 11:01:54 -0700392
393 // Hotseat
394 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
395 hotseatCellWidthPx = iconSizePx;
396 hotseatCellHeightPx = iconSizePx;
397
398 // Folder
399 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
400 folderCellHeightPx = cellHeightPx + edgeMarginPx;
401 folderBackgroundOffset = -edgeMarginPx;
402 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
403
404 // All Apps
405 Rect padding = getWorkspacePadding(isLandscape ?
406 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
407 int pageIndicatorOffset =
408 resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
409 allAppsCellWidthPx = allAppsIconSizePx;
410 allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
411 int maxLongEdgeCellCount =
412 resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
413 int maxShortEdgeCellCount =
414 resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
415 int minEdgeCellCount =
416 resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
417 int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
418 int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
419
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700420 if (allAppsShortEdgeCount > 0 && allAppsLongEdgeCount > 0) {
421 allAppsNumRows = isLandscape ? allAppsShortEdgeCount : allAppsLongEdgeCount;
422 allAppsNumCols = isLandscape ? allAppsLongEdgeCount : allAppsShortEdgeCount;
423 } else {
424 allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
425 (allAppsCellHeightPx + allAppsCellPaddingPx);
426 allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
427 allAppsNumCols = (availableWidthPx) /
428 (allAppsCellWidthPx + allAppsCellPaddingPx);
429 allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
430 }
Winson Chungb3800242013-10-24 11:01:54 -0700431 }
432
433 void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
434 int awPx, int ahPx) {
Winson Chung42b3c062013-12-04 12:09:59 -0800435 Configuration configuration = resources.getConfiguration();
436 isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
Winson Chungb3800242013-10-24 11:01:54 -0700437 isTablet = resources.getBoolean(R.bool.is_tablet);
438 isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
Winson Chung6033ceb2014-02-05 12:37:42 -0800439 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
440 isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
441 } else {
442 isLayoutRtl = false;
443 }
Winson Chungb3800242013-10-24 11:01:54 -0700444 widthPx = wPx;
445 heightPx = hPx;
446 availableWidthPx = awPx;
447 availableHeightPx = ahPx;
448
449 updateAvailableDimensions(context);
450 }
451
452 private float dist(PointF p0, PointF p1) {
453 return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
454 (p1.y-p0.y)*(p1.y-p0.y));
455 }
456
457 private float weight(PointF a, PointF b,
458 float pow) {
459 float d = dist(a, b);
460 if (d == 0f) {
461 return Float.POSITIVE_INFINITY;
462 }
463 return (float) (1f / Math.pow(d, pow));
464 }
465
Winson Chungbe876472014-05-14 14:15:20 -0700466 /** Returns the closest device profile given the width and height and a list of profiles */
467 private DeviceProfile findClosestDeviceProfile(float width, float height,
468 ArrayList<DeviceProfileQuery> points) {
469 return findClosestDeviceProfiles(width, height, points).get(0).profile;
470 }
471
472 /** Returns the closest device profiles ordered by closeness to the specified width and height */
473 private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height,
474 ArrayList<DeviceProfileQuery> points) {
475 final PointF xy = new PointF(width, height);
476
477 // Sort the profiles by their closeness to the dimensions
478 ArrayList<DeviceProfileQuery> pointsByNearness = points;
479 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
480 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
481 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
482 }
483 });
484
485 return pointsByNearness;
486 }
487
Winson Chungb3800242013-10-24 11:01:54 -0700488 private float invDistWeightedInterpolate(float width, float height,
489 ArrayList<DeviceProfileQuery> points) {
490 float sum = 0;
491 float weights = 0;
492 float pow = 5;
493 float kNearestNeighbors = 3;
494 final PointF xy = new PointF(width, height);
495
Winson Chungbe876472014-05-14 14:15:20 -0700496 ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height,
497 points);
Winson Chungb3800242013-10-24 11:01:54 -0700498
499 for (int i = 0; i < pointsByNearness.size(); ++i) {
500 DeviceProfileQuery p = pointsByNearness.get(i);
501 if (i < kNearestNeighbors) {
502 float w = weight(xy, p.dimens, pow);
503 if (w == Float.POSITIVE_INFINITY) {
504 return p.value;
505 }
506 weights += w;
507 }
508 }
509
510 for (int i = 0; i < pointsByNearness.size(); ++i) {
511 DeviceProfileQuery p = pointsByNearness.get(i);
512 if (i < kNearestNeighbors) {
513 float w = weight(xy, p.dimens, pow);
514 sum += w * p.value / weights;
515 }
516 }
517
518 return sum;
519 }
520
Winson Chung69e04ea2013-12-02 14:43:44 -0800521 /** Returns the search bar top offset */
522 int getSearchBarTopOffset() {
523 if (isTablet() && !isVerticalBarLayout()) {
524 return 4 * edgeMarginPx;
525 } else {
526 return 2 * edgeMarginPx;
527 }
528 }
529
Winson Chungb3800242013-10-24 11:01:54 -0700530 /** Returns the search bar bounds in the current orientation */
531 Rect getSearchBarBounds() {
532 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
533 }
534 /** Returns the search bar bounds in the specified orientation */
535 Rect getSearchBarBounds(int orientation) {
536 Rect bounds = new Rect();
537 if (orientation == CellLayout.LANDSCAPE &&
538 transposeLayoutWithOrientation) {
Winson Chung42b3c062013-12-04 12:09:59 -0800539 if (isLayoutRtl) {
540 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
541 availableWidthPx, availableHeightPx - edgeMarginPx);
542 } else {
543 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
544 availableHeightPx - edgeMarginPx);
545 }
Winson Chungb3800242013-10-24 11:01:54 -0700546 } else {
547 if (isTablet()) {
548 // Pad the left and right of the workspace to ensure consistent spacing
549 // between all icons
550 int width = (orientation == CellLayout.LANDSCAPE)
551 ? Math.max(widthPx, heightPx)
552 : Math.min(widthPx, heightPx);
553 // XXX: If the icon size changes across orientations, we will have to take
554 // that into account here too.
555 int gap = (int) ((width - 2 * edgeMarginPx -
556 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
Winson Chung2cb24712013-12-02 15:00:39 -0800557 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
558 availableWidthPx - (edgeMarginPx + gap),
Winson Chungb3800242013-10-24 11:01:54 -0700559 searchBarSpaceHeightPx);
560 } else {
Winson Chung2cb24712013-12-02 15:00:39 -0800561 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
562 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700563 availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
564 defaultWidgetPadding.right), searchBarSpaceHeightPx);
565 }
566 }
567 return bounds;
568 }
569
Winson Chunga6945242014-01-08 14:04:34 -0800570 /** Returns the bounds of the workspace page indicators. */
571 Rect getWorkspacePageIndicatorBounds(Rect insets) {
572 Rect workspacePadding = getWorkspacePadding();
Winson Chung205cd772014-01-15 14:31:59 -0800573 if (isLandscape && transposeLayoutWithOrientation) {
574 if (isLayoutRtl) {
575 return new Rect(workspacePadding.left, workspacePadding.top,
576 workspacePadding.left + pageIndicatorHeightPx,
577 heightPx - workspacePadding.bottom - insets.bottom);
578 } else {
579 int pageIndicatorLeft = widthPx - workspacePadding.right;
580 return new Rect(pageIndicatorLeft, workspacePadding.top,
581 pageIndicatorLeft + pageIndicatorHeightPx,
582 heightPx - workspacePadding.bottom - insets.bottom);
583 }
584 } else {
585 int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom;
586 return new Rect(workspacePadding.left, pageIndicatorTop,
587 widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx);
588 }
Winson Chunga6945242014-01-08 14:04:34 -0800589 }
590
Winson Chungb3800242013-10-24 11:01:54 -0700591 /** Returns the workspace padding in the specified orientation */
592 Rect getWorkspacePadding() {
593 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
594 }
595 Rect getWorkspacePadding(int orientation) {
596 Rect searchBarBounds = getSearchBarBounds(orientation);
597 Rect padding = new Rect();
598 if (orientation == CellLayout.LANDSCAPE &&
599 transposeLayoutWithOrientation) {
600 // Pad the left and right of the workspace with search/hotseat bar sizes
Winson Chung42b3c062013-12-04 12:09:59 -0800601 if (isLayoutRtl) {
602 padding.set(hotseatBarHeightPx, edgeMarginPx,
603 searchBarBounds.width(), edgeMarginPx);
604 } else {
605 padding.set(searchBarBounds.width(), edgeMarginPx,
606 hotseatBarHeightPx, edgeMarginPx);
607 }
Winson Chungb3800242013-10-24 11:01:54 -0700608 } else {
609 if (isTablet()) {
610 // Pad the left and right of the workspace to ensure consistent spacing
611 // between all icons
Winson Chung59a488a2013-12-10 12:32:14 -0800612 float gapScale = 1f + (dragViewScale - 1f) / 2f;
Winson Chungb3800242013-10-24 11:01:54 -0700613 int width = (orientation == CellLayout.LANDSCAPE)
614 ? Math.max(widthPx, heightPx)
615 : Math.min(widthPx, heightPx);
Winson Chung59a488a2013-12-10 12:32:14 -0800616 int height = (orientation != CellLayout.LANDSCAPE)
617 ? Math.max(widthPx, heightPx)
618 : Math.min(widthPx, heightPx);
619 int paddingTop = searchBarBounds.bottom;
620 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
621 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
622 (numColumns * gapScale * cellWidthPx)));
623 int availableHeight = Math.max(0, height - paddingTop - paddingBottom
624 - (int) (2 * numRows * cellHeightPx));
625 padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
626 availableWidth / 2, paddingBottom + availableHeight / 2);
Winson Chungb3800242013-10-24 11:01:54 -0700627 } else {
628 // Pad the top and bottom of the workspace with search/hotseat bar sizes
629 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
630 searchBarBounds.bottom,
631 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
632 hotseatBarHeightPx + pageIndicatorHeightPx);
633 }
634 }
635 return padding;
636 }
637
638 int getWorkspacePageSpacing(int orientation) {
Winson Chung59a488a2013-12-10 12:32:14 -0800639 if ((orientation == CellLayout.LANDSCAPE &&
640 transposeLayoutWithOrientation) || isLargeTablet()) {
Winson Chungb3800242013-10-24 11:01:54 -0700641 // In landscape mode the page spacing is set to the default.
642 return defaultPageSpacingPx;
643 } else {
644 // In portrait, we want the pages spaced such that there is no
645 // overhang of the previous / next page into the current page viewport.
646 // We assume symmetrical padding in portrait mode.
Adam Cohenefb31e32014-01-16 16:07:50 -0800647 return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
Winson Chungb3800242013-10-24 11:01:54 -0700648 }
649 }
650
651 Rect getOverviewModeButtonBarRect() {
652 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
653 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
654 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
655 return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
656 }
657
658 float getOverviewModeScale() {
659 Rect workspacePadding = getWorkspacePadding();
660 Rect overviewBar = getOverviewModeButtonBarRect();
661 int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
662 return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
663 }
664
665 // The rect returned will be extended to below the system ui that covers the workspace
666 Rect getHotseatRect() {
667 if (isVerticalBarLayout()) {
668 return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
669 Integer.MAX_VALUE, availableHeightPx);
670 } else {
671 return new Rect(0, availableHeightPx - hotseatBarHeightPx,
672 availableWidthPx, Integer.MAX_VALUE);
673 }
674 }
675
676 int calculateCellWidth(int width, int countX) {
677 return width / countX;
678 }
679 int calculateCellHeight(int height, int countY) {
680 return height / countY;
681 }
682
683 boolean isPhone() {
684 return !isTablet && !isLargeTablet;
685 }
686 boolean isTablet() {
687 return isTablet;
688 }
689 boolean isLargeTablet() {
690 return isLargeTablet;
691 }
692
693 boolean isVerticalBarLayout() {
694 return isLandscape && transposeLayoutWithOrientation;
695 }
696
697 boolean shouldFadeAdjacentWorkspaceScreens() {
698 return isVerticalBarLayout() || isLargeTablet();
699 }
700
Jorim Jaggid017f882014-01-14 17:08:48 -0800701 int getVisibleChildCount(ViewGroup parent) {
702 int visibleChildren = 0;
703 for (int i = 0; i < parent.getChildCount(); i++) {
704 if (parent.getChildAt(i).getVisibility() != View.GONE) {
705 visibleChildren++;
706 }
707 }
708 return visibleChildren;
709 }
710
711 int calculateOverviewModeWidth(int visibleChildCount) {
712 return visibleChildCount * overviewModeBarItemWidthPx +
713 (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
714 }
715
Winson Chungb3800242013-10-24 11:01:54 -0700716 public void layout(Launcher launcher) {
717 FrameLayout.LayoutParams lp;
718 Resources res = launcher.getResources();
719 boolean hasVerticalBarLayout = isVerticalBarLayout();
720
721 // Layout the search bar space
722 View searchBar = launcher.getSearchBar();
723 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
724 if (hasVerticalBarLayout) {
Winson Chung69e04ea2013-12-02 14:43:44 -0800725 // Vertical search bar space
Winson Chungb3800242013-10-24 11:01:54 -0700726 lp.gravity = Gravity.TOP | Gravity.LEFT;
727 lp.width = searchBarSpaceHeightPx;
Adam Cohen24ce0b32014-01-14 16:18:14 -0800728 lp.height = LayoutParams.WRAP_CONTENT;
Winson Chungb3800242013-10-24 11:01:54 -0700729 searchBar.setPadding(
730 0, 2 * edgeMarginPx, 0,
731 2 * edgeMarginPx);
Adam Cohen24ce0b32014-01-14 16:18:14 -0800732
733 LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
734 targets.setOrientation(LinearLayout.VERTICAL);
Winson Chungb3800242013-10-24 11:01:54 -0700735 } else {
Winson Chung69e04ea2013-12-02 14:43:44 -0800736 // Horizontal search bar space
Winson Chungb3800242013-10-24 11:01:54 -0700737 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
738 lp.width = searchBarSpaceWidthPx;
739 lp.height = searchBarSpaceHeightPx;
740 searchBar.setPadding(
741 2 * edgeMarginPx,
Winson Chung69e04ea2013-12-02 14:43:44 -0800742 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700743 2 * edgeMarginPx, 0);
744 }
745 searchBar.setLayoutParams(lp);
746
Winson Chungb3800242013-10-24 11:01:54 -0700747 // Layout the voice proxy
748 View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
749 if (voiceButtonProxy != null) {
750 if (hasVerticalBarLayout) {
751 // TODO: MOVE THIS INTO SEARCH BAR MEASURE
752 } else {
753 lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
754 lp.gravity = Gravity.TOP | Gravity.END;
755 lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
756 2 * iconSizePx;
757 lp.height = searchBarSpaceHeightPx;
758 }
759 }
760
761 // Layout the workspace
762 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
763 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
764 lp.gravity = Gravity.CENTER;
765 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
766 Rect padding = getWorkspacePadding(orientation);
767 workspace.setLayoutParams(lp);
768 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
769 workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
770
771 // Layout the hotseat
772 View hotseat = launcher.findViewById(R.id.hotseat);
773 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
774 if (hasVerticalBarLayout) {
775 // Vertical hotseat
Winson Chung42b3c062013-12-04 12:09:59 -0800776 lp.gravity = Gravity.END;
Winson Chungb3800242013-10-24 11:01:54 -0700777 lp.width = hotseatBarHeightPx;
778 lp.height = LayoutParams.MATCH_PARENT;
779 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
780 } else if (isTablet()) {
Winson Chung59a488a2013-12-10 12:32:14 -0800781 // Pad the hotseat with the workspace padding calculated above
Winson Chungb3800242013-10-24 11:01:54 -0700782 lp.gravity = Gravity.BOTTOM;
783 lp.width = LayoutParams.MATCH_PARENT;
784 lp.height = hotseatBarHeightPx;
Winson Chung59a488a2013-12-10 12:32:14 -0800785 hotseat.setPadding(edgeMarginPx + padding.left, 0,
786 edgeMarginPx + padding.right,
Winson Chungb3800242013-10-24 11:01:54 -0700787 2 * edgeMarginPx);
788 } else {
789 // For phones, layout the hotseat without any bottom margin
790 // to ensure that we have space for the folders
791 lp.gravity = Gravity.BOTTOM;
792 lp.width = LayoutParams.MATCH_PARENT;
793 lp.height = hotseatBarHeightPx;
794 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
795 2 * edgeMarginPx, 0);
796 }
797 hotseat.setLayoutParams(lp);
798
799 // Layout the page indicators
800 View pageIndicator = launcher.findViewById(R.id.page_indicator);
801 if (pageIndicator != null) {
802 if (hasVerticalBarLayout) {
803 // Hide the page indicators when we have vertical search/hotseat
804 pageIndicator.setVisibility(View.GONE);
805 } else {
806 // Put the page indicators above the hotseat
807 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
808 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
809 lp.width = LayoutParams.WRAP_CONTENT;
810 lp.height = LayoutParams.WRAP_CONTENT;
811 lp.bottomMargin = hotseatBarHeightPx;
812 pageIndicator.setLayoutParams(lp);
813 }
814 }
815
816 // Layout AllApps
817 AppsCustomizeTabHost host = (AppsCustomizeTabHost)
818 launcher.findViewById(R.id.apps_customize_pane);
819 if (host != null) {
820 // Center the all apps page indicator
821 int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
822 (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
823 pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
824 if (pageIndicator != null) {
Adam Cohen96bb7982014-07-07 11:58:56 -0700825 LinearLayout.LayoutParams lllp = (LinearLayout.LayoutParams) pageIndicator.getLayoutParams();
826 lllp.width = LayoutParams.WRAP_CONTENT;
827 lllp.height = pageIndicatorHeight;
828 pageIndicator.setLayoutParams(lllp);
Winson Chungb3800242013-10-24 11:01:54 -0700829 }
830
831 AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
832 host.findViewById(R.id.apps_customize_pane_content);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700833
834 FrameLayout fakePageContainer = (FrameLayout)
835 host.findViewById(R.id.fake_page_container);
836 FrameLayout fakePage = (FrameLayout) host.findViewById(R.id.fake_page);
837
Winson Chungb3800242013-10-24 11:01:54 -0700838 padding = new Rect();
839 if (pagedView != null) {
840 // Constrain the dimensions of all apps so that it does not span the full width
841 int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
842 (2 * (allAppsNumCols + 1));
843 int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
844 (2 * (allAppsNumRows + 1));
845 paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
846 paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
847 int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
848 int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
Winson Chung495f44d2013-12-04 12:51:53 -0800849 // Only adjust the side paddings on landscape phones, or tablets
850 if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
Winson Chungb3800242013-10-24 11:01:54 -0700851 padding.left = padding.right = gridPaddingLR;
852 }
Adam Cohen9bfdb762014-07-21 17:44:06 -0700853
Winson Chungb3800242013-10-24 11:01:54 -0700854 // The icons are centered, so we can't just offset by the page indicator height
855 // because the empty space will actually be pageIndicatorHeight + paddingTB
856 padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700857
Winson Chungb3800242013-10-24 11:01:54 -0700858 pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700859 fakePage.setBackground(res.getDrawable(R.drawable.quantum_panel));
Adam Cohen96bb7982014-07-07 11:58:56 -0700860
861 // Horizontal padding for the whole paged view
Adam Cohen9bfdb762014-07-21 17:44:06 -0700862 int pagedFixedViewPadding =
Adam Cohen96bb7982014-07-07 11:58:56 -0700863 res.getDimensionPixelSize(R.dimen.apps_customize_horizontal_padding);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700864
865 padding.left += pagedFixedViewPadding;
866 padding.right += pagedFixedViewPadding;
867
868 pagedView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
869 fakePageContainer.setPadding(padding.left, padding.top, padding.right, padding.bottom);
870
Winson Chungb3800242013-10-24 11:01:54 -0700871 }
872 }
873
874 // Layout the Overview Mode
Jorim Jaggid017f882014-01-14 17:08:48 -0800875 ViewGroup overviewMode = launcher.getOverviewPanel();
Winson Chungb3800242013-10-24 11:01:54 -0700876 if (overviewMode != null) {
877 Rect r = getOverviewModeButtonBarRect();
878 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
879 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
Jorim Jaggid017f882014-01-14 17:08:48 -0800880 lp.width = Math.min(availableWidthPx,
881 calculateOverviewModeWidth(getVisibleChildCount(overviewMode)));
Winson Chungb3800242013-10-24 11:01:54 -0700882 lp.height = r.height();
883 overviewMode.setLayoutParams(lp);
884 }
885 }
886}