blob: b2366bb2bd43ce2c6a7cf24362120fdea182f0f1 [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;
Winson Chungbe876472014-05-14 14:15:20 -070078
Winson Chungb3800242013-10-24 11:01:54 -070079 boolean isLandscape;
80 boolean isTablet;
81 boolean isLargeTablet;
Winson Chung42b3c062013-12-04 12:09:59 -080082 boolean isLayoutRtl;
Winson Chungb3800242013-10-24 11:01:54 -070083 boolean transposeLayoutWithOrientation;
84
85 int desiredWorkspaceLeftRightMarginPx;
86 int edgeMarginPx;
87 Rect defaultWidgetPadding;
88
89 int widthPx;
90 int heightPx;
91 int availableWidthPx;
92 int availableHeightPx;
93 int defaultPageSpacingPx;
94
95 int overviewModeMinIconZoneHeightPx;
96 int overviewModeMaxIconZoneHeightPx;
Jorim Jaggid017f882014-01-14 17:08:48 -080097 int overviewModeBarItemWidthPx;
98 int overviewModeBarSpacerWidthPx;
Winson Chungb3800242013-10-24 11:01:54 -070099 float overviewModeIconZoneRatio;
100 float overviewModeScaleFactor;
101
102 int iconSizePx;
103 int iconTextSizePx;
104 int iconDrawablePaddingPx;
105 int cellWidthPx;
106 int cellHeightPx;
107 int allAppsIconSizePx;
108 int allAppsIconTextSizePx;
109 int allAppsCellWidthPx;
110 int allAppsCellHeightPx;
111 int allAppsCellPaddingPx;
112 int folderBackgroundOffset;
113 int folderIconSizePx;
114 int folderCellWidthPx;
115 int folderCellHeightPx;
116 int hotseatCellWidthPx;
117 int hotseatCellHeightPx;
118 int hotseatIconSizePx;
119 int hotseatBarHeightPx;
120 int hotseatAllAppsRank;
121 int allAppsNumRows;
122 int allAppsNumCols;
123 int searchBarSpaceWidthPx;
124 int searchBarSpaceMaxWidthPx;
125 int searchBarSpaceHeightPx;
126 int searchBarHeightPx;
127 int pageIndicatorHeightPx;
Adam Cohen63f1ec02014-08-12 09:23:13 -0700128 int allAppsButtonVisualSize;
Winson Chungb3800242013-10-24 11:01:54 -0700129
Winson Chung59a488a2013-12-10 12:32:14 -0800130 float dragViewScale;
131
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700132 int allAppsShortEdgeCount = -1;
133 int allAppsLongEdgeCount = -1;
134
Winson Chungb3800242013-10-24 11:01:54 -0700135 private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
136
137 DeviceProfile(String n, float w, float h, float r, float c,
Adam Cohencee8c662014-10-16 09:49:52 -0700138 float is, float its, float hs, float his, int dlId) {
Winson Chungb3800242013-10-24 11:01:54 -0700139 // Ensure that we have an odd number of hotseat items (since we need to place all apps)
Nilesh Agrawal16f3ea82014-01-09 17:14:01 -0800140 if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) {
Winson Chungb3800242013-10-24 11:01:54 -0700141 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
142 }
143
144 name = n;
145 minWidthDps = w;
146 minHeightDps = h;
147 numRows = r;
148 numColumns = c;
149 iconSize = is;
150 iconTextSize = its;
151 numHotseatIcons = hs;
152 hotseatIconSize = his;
Winson Chungbe876472014-05-14 14:15:20 -0700153 defaultLayoutId = dlId;
Winson Chungb3800242013-10-24 11:01:54 -0700154 }
155
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700156 DeviceProfile() {
157 }
158
Winson Chungb3800242013-10-24 11:01:54 -0700159 DeviceProfile(Context context,
160 ArrayList<DeviceProfile> profiles,
161 float minWidth, float minHeight,
162 int wPx, int hPx,
163 int awPx, int ahPx,
164 Resources res) {
165 DisplayMetrics dm = res.getDisplayMetrics();
166 ArrayList<DeviceProfileQuery> points =
167 new ArrayList<DeviceProfileQuery>();
168 transposeLayoutWithOrientation =
169 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
170 minWidthDps = minWidth;
171 minHeightDps = minHeight;
172
173 ComponentName cn = new ComponentName(context.getPackageName(),
174 this.getClass().getName());
175 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
176 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
177 desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
178 pageIndicatorHeightPx =
179 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
180 defaultPageSpacingPx =
181 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
182 allAppsCellPaddingPx =
183 res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
184 overviewModeMinIconZoneHeightPx =
185 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
186 overviewModeMaxIconZoneHeightPx =
187 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
Jorim Jaggid017f882014-01-14 17:08:48 -0800188 overviewModeBarItemWidthPx =
189 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
190 overviewModeBarSpacerWidthPx =
191 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
Winson Chungb3800242013-10-24 11:01:54 -0700192 overviewModeIconZoneRatio =
193 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
194 overviewModeScaleFactor =
195 res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
196
Winson Chungbe876472014-05-14 14:15:20 -0700197 // Find the closes profile given the width/height
Winson Chungb3800242013-10-24 11:01:54 -0700198 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700199 points.add(new DeviceProfileQuery(p, 0f));
Winson Chungb3800242013-10-24 11:01:54 -0700200 }
Winson Chungbe876472014-05-14 14:15:20 -0700201 DeviceProfile closestProfile = findClosestDeviceProfile(minWidth, minHeight, points);
202
203 // Snap to the closest row count
204 numRows = closestProfile.numRows;
205
206 // Snap to the closest column count
207 numColumns = closestProfile.numColumns;
208
209 // Snap to the closest hotseat size
210 numHotseatIcons = closestProfile.numHotseatIcons;
Winson Chungb3800242013-10-24 11:01:54 -0700211 hotseatAllAppsRank = (int) (numHotseatIcons / 2);
212
Winson Chungbe876472014-05-14 14:15:20 -0700213 // Snap to the closest default layout id
214 defaultLayoutId = closestProfile.defaultLayoutId;
215
Winson Chungb3800242013-10-24 11:01:54 -0700216 // Interpolate the icon size
217 points.clear();
218 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700219 points.add(new DeviceProfileQuery(p, p.iconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700220 }
221 iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700222
Winson Chungb3800242013-10-24 11:01:54 -0700223 // AllApps uses the original non-scaled icon size
224 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
225
226 // Interpolate the icon text size
227 points.clear();
228 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700229 points.add(new DeviceProfileQuery(p, p.iconTextSize));
Winson Chungb3800242013-10-24 11:01:54 -0700230 }
231 iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
232 iconDrawablePaddingOriginalPx =
233 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
234 // AllApps uses the original non-scaled icon text size
235 allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
236
237 // Interpolate the hotseat icon size
238 points.clear();
239 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700240 points.add(new DeviceProfileQuery(p, p.hotseatIconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700241 }
242 // Hotseat
243 hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
244
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700245 // If the partner customization apk contains any grid overrides, apply them
246 applyPartnerDeviceProfileOverrides(context, dm);
247
Winson Chungb3800242013-10-24 11:01:54 -0700248 // Calculate the remaining vars
249 updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
250 updateAvailableDimensions(context);
Adam Cohen63f1ec02014-08-12 09:23:13 -0700251 computeAllAppsButtonSize(context);
252 }
253
254 /**
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700255 * Apply any Partner customization grid overrides.
256 *
257 * Currently we support: all apps row / column count.
258 */
259 private void applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm) {
260 Partner p = Partner.get(ctx.getPackageManager());
261 if (p != null) {
262 DeviceProfile partnerDp = p.getDeviceProfileOverride(dm);
263 if (partnerDp != null) {
264 if (partnerDp.numRows > 0 && partnerDp.numColumns > 0) {
265 numRows = partnerDp.numRows;
266 numColumns = partnerDp.numColumns;
267 }
268 if (partnerDp.allAppsShortEdgeCount > 0 && partnerDp.allAppsLongEdgeCount > 0) {
269 allAppsShortEdgeCount = partnerDp.allAppsShortEdgeCount;
270 allAppsLongEdgeCount = partnerDp.allAppsLongEdgeCount;
271 }
272 if (partnerDp.iconSize > 0) {
273 iconSize = partnerDp.iconSize;
274 // AllApps uses the original non-scaled icon size
275 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
276 }
277 }
278 }
279 }
280
281 /**
Adam Cohen63f1ec02014-08-12 09:23:13 -0700282 * Determine the exact visual footprint of the all apps button, taking into account scaling
283 * and internal padding of the drawable.
284 */
285 private void computeAllAppsButtonSize(Context context) {
286 Resources res = context.getResources();
287 float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
288 LauncherAppState app = LauncherAppState.getInstance();
289 allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding));
Winson Chungb3800242013-10-24 11:01:54 -0700290 }
291
292 void addCallback(DeviceProfileCallbacks cb) {
293 mCallbacks.add(cb);
294 cb.onAvailableSizeChanged(this);
295 }
296 void removeCallback(DeviceProfileCallbacks cb) {
297 mCallbacks.remove(cb);
298 }
299
300 private int getDeviceOrientation(Context context) {
301 WindowManager windowManager = (WindowManager)
302 context.getSystemService(Context.WINDOW_SERVICE);
303 Resources resources = context.getResources();
304 DisplayMetrics dm = resources.getDisplayMetrics();
305 Configuration config = resources.getConfiguration();
306 int rotation = windowManager.getDefaultDisplay().getRotation();
307
308 boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
309 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
310 boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
311 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
312 if (isLandscape || isRotatedPortrait) {
313 return CellLayout.LANDSCAPE;
314 } else {
315 return CellLayout.PORTRAIT;
316 }
317 }
318
319 private void updateAvailableDimensions(Context context) {
320 WindowManager windowManager = (WindowManager)
321 context.getSystemService(Context.WINDOW_SERVICE);
322 Display display = windowManager.getDefaultDisplay();
323 Resources resources = context.getResources();
324 DisplayMetrics dm = resources.getDisplayMetrics();
325 Configuration config = resources.getConfiguration();
326
327 // There are three possible configurations that the dynamic grid accounts for, portrait,
328 // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
329 // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
330 // the height is the smallest height (either with the nav bar at the bottom or to the
331 // side) and otherwise, the height is simply the largest possible height for a portrait
332 // device.
333 Point size = new Point();
334 Point smallestSize = new Point();
335 Point largestSize = new Point();
336 display.getSize(size);
337 display.getCurrentSizeRange(smallestSize, largestSize);
338 availableWidthPx = size.x;
339 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
340 availableHeightPx = smallestSize.y;
341 } else {
342 availableHeightPx = largestSize.y;
343 }
344
345 // Check to see if the icons fit in the new available height. If not, then we need to
346 // shrink the icon size.
Winson Chungb3800242013-10-24 11:01:54 -0700347 float scale = 1f;
348 int drawablePadding = iconDrawablePaddingOriginalPx;
349 updateIconSize(1f, drawablePadding, resources, dm);
350 float usedHeight = (cellHeightPx * numRows);
Winson Chung59a488a2013-12-10 12:32:14 -0800351
352 Rect workspacePadding = getWorkspacePadding();
Winson Chungb3800242013-10-24 11:01:54 -0700353 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
354 if (usedHeight > maxHeight) {
355 scale = maxHeight / usedHeight;
356 drawablePadding = 0;
357 }
358 updateIconSize(scale, drawablePadding, resources, dm);
359
360 // Make the callbacks
361 for (DeviceProfileCallbacks cb : mCallbacks) {
362 cb.onAvailableSizeChanged(this);
363 }
364 }
365
366 private void updateIconSize(float scale, int drawablePadding, Resources resources,
367 DisplayMetrics dm) {
368 iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
369 iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
370 iconDrawablePaddingPx = drawablePadding;
371 hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
372
373 // Search Bar
374 searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
375 searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
376 searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
Winson Chung69e04ea2013-12-02 14:43:44 -0800377 searchBarSpaceHeightPx = searchBarHeightPx + getSearchBarTopOffset();
Winson Chungb3800242013-10-24 11:01:54 -0700378
379 // Calculate the actual text height
380 Paint textPaint = new Paint();
381 textPaint.setTextSize(iconTextSizePx);
382 FontMetrics fm = textPaint.getFontMetrics();
383 cellWidthPx = iconSizePx;
384 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
Winson Chung59a488a2013-12-10 12:32:14 -0800385 final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale);
386 dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
Winson Chungb3800242013-10-24 11:01:54 -0700387
388 // Hotseat
389 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
390 hotseatCellWidthPx = iconSizePx;
391 hotseatCellHeightPx = iconSizePx;
392
393 // Folder
394 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
395 folderCellHeightPx = cellHeightPx + edgeMarginPx;
396 folderBackgroundOffset = -edgeMarginPx;
397 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
398
399 // All Apps
400 Rect padding = getWorkspacePadding(isLandscape ?
401 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
402 int pageIndicatorOffset =
403 resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
404 allAppsCellWidthPx = allAppsIconSizePx;
405 allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
406 int maxLongEdgeCellCount =
407 resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
408 int maxShortEdgeCellCount =
409 resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
410 int minEdgeCellCount =
411 resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
412 int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
413 int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
414
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700415 if (allAppsShortEdgeCount > 0 && allAppsLongEdgeCount > 0) {
416 allAppsNumRows = isLandscape ? allAppsShortEdgeCount : allAppsLongEdgeCount;
417 allAppsNumCols = isLandscape ? allAppsLongEdgeCount : allAppsShortEdgeCount;
418 } else {
419 allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
420 (allAppsCellHeightPx + allAppsCellPaddingPx);
421 allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
422 allAppsNumCols = (availableWidthPx) /
423 (allAppsCellWidthPx + allAppsCellPaddingPx);
424 allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
425 }
Winson Chungb3800242013-10-24 11:01:54 -0700426 }
427
428 void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
429 int awPx, int ahPx) {
Winson Chung42b3c062013-12-04 12:09:59 -0800430 Configuration configuration = resources.getConfiguration();
431 isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
Winson Chungb3800242013-10-24 11:01:54 -0700432 isTablet = resources.getBoolean(R.bool.is_tablet);
433 isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
Winson Chung6033ceb2014-02-05 12:37:42 -0800434 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
435 isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
436 } else {
437 isLayoutRtl = false;
438 }
Winson Chungb3800242013-10-24 11:01:54 -0700439 widthPx = wPx;
440 heightPx = hPx;
441 availableWidthPx = awPx;
442 availableHeightPx = ahPx;
443
444 updateAvailableDimensions(context);
445 }
446
447 private float dist(PointF p0, PointF p1) {
448 return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
449 (p1.y-p0.y)*(p1.y-p0.y));
450 }
451
452 private float weight(PointF a, PointF b,
453 float pow) {
454 float d = dist(a, b);
455 if (d == 0f) {
456 return Float.POSITIVE_INFINITY;
457 }
458 return (float) (1f / Math.pow(d, pow));
459 }
460
Winson Chungbe876472014-05-14 14:15:20 -0700461 /** Returns the closest device profile given the width and height and a list of profiles */
462 private DeviceProfile findClosestDeviceProfile(float width, float height,
463 ArrayList<DeviceProfileQuery> points) {
464 return findClosestDeviceProfiles(width, height, points).get(0).profile;
465 }
466
467 /** Returns the closest device profiles ordered by closeness to the specified width and height */
468 private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height,
469 ArrayList<DeviceProfileQuery> points) {
470 final PointF xy = new PointF(width, height);
471
472 // Sort the profiles by their closeness to the dimensions
473 ArrayList<DeviceProfileQuery> pointsByNearness = points;
474 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
475 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
476 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
477 }
478 });
479
480 return pointsByNearness;
481 }
482
Winson Chungb3800242013-10-24 11:01:54 -0700483 private float invDistWeightedInterpolate(float width, float height,
484 ArrayList<DeviceProfileQuery> points) {
485 float sum = 0;
486 float weights = 0;
487 float pow = 5;
488 float kNearestNeighbors = 3;
489 final PointF xy = new PointF(width, height);
490
Winson Chungbe876472014-05-14 14:15:20 -0700491 ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height,
492 points);
Winson Chungb3800242013-10-24 11:01:54 -0700493
494 for (int i = 0; i < pointsByNearness.size(); ++i) {
495 DeviceProfileQuery p = pointsByNearness.get(i);
496 if (i < kNearestNeighbors) {
497 float w = weight(xy, p.dimens, pow);
498 if (w == Float.POSITIVE_INFINITY) {
499 return p.value;
500 }
501 weights += w;
502 }
503 }
504
505 for (int i = 0; i < pointsByNearness.size(); ++i) {
506 DeviceProfileQuery p = pointsByNearness.get(i);
507 if (i < kNearestNeighbors) {
508 float w = weight(xy, p.dimens, pow);
509 sum += w * p.value / weights;
510 }
511 }
512
513 return sum;
514 }
515
Winson Chung69e04ea2013-12-02 14:43:44 -0800516 /** Returns the search bar top offset */
517 int getSearchBarTopOffset() {
518 if (isTablet() && !isVerticalBarLayout()) {
519 return 4 * edgeMarginPx;
520 } else {
521 return 2 * edgeMarginPx;
522 }
523 }
524
Winson Chungb3800242013-10-24 11:01:54 -0700525 /** Returns the search bar bounds in the current orientation */
526 Rect getSearchBarBounds() {
527 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
528 }
529 /** Returns the search bar bounds in the specified orientation */
530 Rect getSearchBarBounds(int orientation) {
531 Rect bounds = new Rect();
532 if (orientation == CellLayout.LANDSCAPE &&
533 transposeLayoutWithOrientation) {
Winson Chung42b3c062013-12-04 12:09:59 -0800534 if (isLayoutRtl) {
535 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
536 availableWidthPx, availableHeightPx - edgeMarginPx);
537 } else {
538 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
539 availableHeightPx - edgeMarginPx);
540 }
Winson Chungb3800242013-10-24 11:01:54 -0700541 } else {
542 if (isTablet()) {
543 // Pad the left and right of the workspace to ensure consistent spacing
544 // between all icons
545 int width = (orientation == CellLayout.LANDSCAPE)
546 ? Math.max(widthPx, heightPx)
547 : Math.min(widthPx, heightPx);
548 // XXX: If the icon size changes across orientations, we will have to take
549 // that into account here too.
550 int gap = (int) ((width - 2 * edgeMarginPx -
551 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
Winson Chung2cb24712013-12-02 15:00:39 -0800552 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
553 availableWidthPx - (edgeMarginPx + gap),
Winson Chungb3800242013-10-24 11:01:54 -0700554 searchBarSpaceHeightPx);
555 } else {
Winson Chung2cb24712013-12-02 15:00:39 -0800556 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
557 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700558 availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
559 defaultWidgetPadding.right), searchBarSpaceHeightPx);
560 }
561 }
562 return bounds;
563 }
564
Winson Chunga6945242014-01-08 14:04:34 -0800565 /** Returns the bounds of the workspace page indicators. */
566 Rect getWorkspacePageIndicatorBounds(Rect insets) {
567 Rect workspacePadding = getWorkspacePadding();
Winson Chung205cd772014-01-15 14:31:59 -0800568 if (isLandscape && transposeLayoutWithOrientation) {
569 if (isLayoutRtl) {
570 return new Rect(workspacePadding.left, workspacePadding.top,
571 workspacePadding.left + pageIndicatorHeightPx,
572 heightPx - workspacePadding.bottom - insets.bottom);
573 } else {
574 int pageIndicatorLeft = widthPx - workspacePadding.right;
575 return new Rect(pageIndicatorLeft, workspacePadding.top,
576 pageIndicatorLeft + pageIndicatorHeightPx,
577 heightPx - workspacePadding.bottom - insets.bottom);
578 }
579 } else {
580 int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom;
581 return new Rect(workspacePadding.left, pageIndicatorTop,
582 widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx);
583 }
Winson Chunga6945242014-01-08 14:04:34 -0800584 }
585
Winson Chungb3800242013-10-24 11:01:54 -0700586 /** Returns the workspace padding in the specified orientation */
587 Rect getWorkspacePadding() {
588 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
589 }
590 Rect getWorkspacePadding(int orientation) {
591 Rect searchBarBounds = getSearchBarBounds(orientation);
592 Rect padding = new Rect();
593 if (orientation == CellLayout.LANDSCAPE &&
594 transposeLayoutWithOrientation) {
595 // Pad the left and right of the workspace with search/hotseat bar sizes
Winson Chung42b3c062013-12-04 12:09:59 -0800596 if (isLayoutRtl) {
597 padding.set(hotseatBarHeightPx, edgeMarginPx,
598 searchBarBounds.width(), edgeMarginPx);
599 } else {
600 padding.set(searchBarBounds.width(), edgeMarginPx,
601 hotseatBarHeightPx, edgeMarginPx);
602 }
Winson Chungb3800242013-10-24 11:01:54 -0700603 } else {
604 if (isTablet()) {
605 // Pad the left and right of the workspace to ensure consistent spacing
606 // between all icons
Winson Chung59a488a2013-12-10 12:32:14 -0800607 float gapScale = 1f + (dragViewScale - 1f) / 2f;
Winson Chungb3800242013-10-24 11:01:54 -0700608 int width = (orientation == CellLayout.LANDSCAPE)
609 ? Math.max(widthPx, heightPx)
610 : Math.min(widthPx, heightPx);
Winson Chung59a488a2013-12-10 12:32:14 -0800611 int height = (orientation != CellLayout.LANDSCAPE)
612 ? Math.max(widthPx, heightPx)
613 : Math.min(widthPx, heightPx);
614 int paddingTop = searchBarBounds.bottom;
615 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
616 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
617 (numColumns * gapScale * cellWidthPx)));
618 int availableHeight = Math.max(0, height - paddingTop - paddingBottom
619 - (int) (2 * numRows * cellHeightPx));
620 padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
621 availableWidth / 2, paddingBottom + availableHeight / 2);
Winson Chungb3800242013-10-24 11:01:54 -0700622 } else {
623 // Pad the top and bottom of the workspace with search/hotseat bar sizes
624 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
625 searchBarBounds.bottom,
626 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
627 hotseatBarHeightPx + pageIndicatorHeightPx);
628 }
629 }
630 return padding;
631 }
632
633 int getWorkspacePageSpacing(int orientation) {
Winson Chung59a488a2013-12-10 12:32:14 -0800634 if ((orientation == CellLayout.LANDSCAPE &&
635 transposeLayoutWithOrientation) || isLargeTablet()) {
Winson Chungb3800242013-10-24 11:01:54 -0700636 // In landscape mode the page spacing is set to the default.
637 return defaultPageSpacingPx;
638 } else {
639 // In portrait, we want the pages spaced such that there is no
640 // overhang of the previous / next page into the current page viewport.
641 // We assume symmetrical padding in portrait mode.
Adam Cohenefb31e32014-01-16 16:07:50 -0800642 return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
Winson Chungb3800242013-10-24 11:01:54 -0700643 }
644 }
645
646 Rect getOverviewModeButtonBarRect() {
647 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
648 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
649 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
650 return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
651 }
652
653 float getOverviewModeScale() {
654 Rect workspacePadding = getWorkspacePadding();
655 Rect overviewBar = getOverviewModeButtonBarRect();
656 int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
657 return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
658 }
659
660 // The rect returned will be extended to below the system ui that covers the workspace
661 Rect getHotseatRect() {
662 if (isVerticalBarLayout()) {
663 return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
664 Integer.MAX_VALUE, availableHeightPx);
665 } else {
666 return new Rect(0, availableHeightPx - hotseatBarHeightPx,
667 availableWidthPx, Integer.MAX_VALUE);
668 }
669 }
670
671 int calculateCellWidth(int width, int countX) {
672 return width / countX;
673 }
674 int calculateCellHeight(int height, int countY) {
675 return height / countY;
676 }
677
678 boolean isPhone() {
679 return !isTablet && !isLargeTablet;
680 }
681 boolean isTablet() {
682 return isTablet;
683 }
684 boolean isLargeTablet() {
685 return isLargeTablet;
686 }
687
688 boolean isVerticalBarLayout() {
689 return isLandscape && transposeLayoutWithOrientation;
690 }
691
692 boolean shouldFadeAdjacentWorkspaceScreens() {
693 return isVerticalBarLayout() || isLargeTablet();
694 }
695
Jorim Jaggid017f882014-01-14 17:08:48 -0800696 int getVisibleChildCount(ViewGroup parent) {
697 int visibleChildren = 0;
698 for (int i = 0; i < parent.getChildCount(); i++) {
699 if (parent.getChildAt(i).getVisibility() != View.GONE) {
700 visibleChildren++;
701 }
702 }
703 return visibleChildren;
704 }
705
706 int calculateOverviewModeWidth(int visibleChildCount) {
707 return visibleChildCount * overviewModeBarItemWidthPx +
708 (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
709 }
710
Winson Chungb3800242013-10-24 11:01:54 -0700711 public void layout(Launcher launcher) {
712 FrameLayout.LayoutParams lp;
713 Resources res = launcher.getResources();
714 boolean hasVerticalBarLayout = isVerticalBarLayout();
715
716 // Layout the search bar space
717 View searchBar = launcher.getSearchBar();
718 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
719 if (hasVerticalBarLayout) {
Winson Chung69e04ea2013-12-02 14:43:44 -0800720 // Vertical search bar space
Winson Chungb3800242013-10-24 11:01:54 -0700721 lp.gravity = Gravity.TOP | Gravity.LEFT;
722 lp.width = searchBarSpaceHeightPx;
Adam Cohen24ce0b32014-01-14 16:18:14 -0800723 lp.height = LayoutParams.WRAP_CONTENT;
Winson Chungb3800242013-10-24 11:01:54 -0700724 searchBar.setPadding(
725 0, 2 * edgeMarginPx, 0,
726 2 * edgeMarginPx);
Adam Cohen24ce0b32014-01-14 16:18:14 -0800727
728 LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
729 targets.setOrientation(LinearLayout.VERTICAL);
Winson Chungb3800242013-10-24 11:01:54 -0700730 } else {
Winson Chung69e04ea2013-12-02 14:43:44 -0800731 // Horizontal search bar space
Winson Chungb3800242013-10-24 11:01:54 -0700732 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
733 lp.width = searchBarSpaceWidthPx;
734 lp.height = searchBarSpaceHeightPx;
735 searchBar.setPadding(
736 2 * edgeMarginPx,
Winson Chung69e04ea2013-12-02 14:43:44 -0800737 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700738 2 * edgeMarginPx, 0);
739 }
740 searchBar.setLayoutParams(lp);
741
Winson Chungb3800242013-10-24 11:01:54 -0700742 // Layout the voice proxy
743 View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
744 if (voiceButtonProxy != null) {
745 if (hasVerticalBarLayout) {
746 // TODO: MOVE THIS INTO SEARCH BAR MEASURE
747 } else {
748 lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
749 lp.gravity = Gravity.TOP | Gravity.END;
750 lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
751 2 * iconSizePx;
752 lp.height = searchBarSpaceHeightPx;
753 }
754 }
755
756 // Layout the workspace
757 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
758 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
759 lp.gravity = Gravity.CENTER;
760 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
761 Rect padding = getWorkspacePadding(orientation);
762 workspace.setLayoutParams(lp);
763 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
764 workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
765
766 // Layout the hotseat
767 View hotseat = launcher.findViewById(R.id.hotseat);
768 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
769 if (hasVerticalBarLayout) {
770 // Vertical hotseat
Winson Chung42b3c062013-12-04 12:09:59 -0800771 lp.gravity = Gravity.END;
Winson Chungb3800242013-10-24 11:01:54 -0700772 lp.width = hotseatBarHeightPx;
773 lp.height = LayoutParams.MATCH_PARENT;
774 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
775 } else if (isTablet()) {
Winson Chung59a488a2013-12-10 12:32:14 -0800776 // Pad the hotseat with the workspace padding calculated above
Winson Chungb3800242013-10-24 11:01:54 -0700777 lp.gravity = Gravity.BOTTOM;
778 lp.width = LayoutParams.MATCH_PARENT;
779 lp.height = hotseatBarHeightPx;
Winson Chung59a488a2013-12-10 12:32:14 -0800780 hotseat.setPadding(edgeMarginPx + padding.left, 0,
781 edgeMarginPx + padding.right,
Winson Chungb3800242013-10-24 11:01:54 -0700782 2 * edgeMarginPx);
783 } else {
784 // For phones, layout the hotseat without any bottom margin
785 // to ensure that we have space for the folders
786 lp.gravity = Gravity.BOTTOM;
787 lp.width = LayoutParams.MATCH_PARENT;
788 lp.height = hotseatBarHeightPx;
789 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
790 2 * edgeMarginPx, 0);
791 }
792 hotseat.setLayoutParams(lp);
793
794 // Layout the page indicators
795 View pageIndicator = launcher.findViewById(R.id.page_indicator);
796 if (pageIndicator != null) {
797 if (hasVerticalBarLayout) {
798 // Hide the page indicators when we have vertical search/hotseat
799 pageIndicator.setVisibility(View.GONE);
800 } else {
801 // Put the page indicators above the hotseat
802 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
803 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
804 lp.width = LayoutParams.WRAP_CONTENT;
805 lp.height = LayoutParams.WRAP_CONTENT;
806 lp.bottomMargin = hotseatBarHeightPx;
807 pageIndicator.setLayoutParams(lp);
808 }
809 }
810
811 // Layout AllApps
812 AppsCustomizeTabHost host = (AppsCustomizeTabHost)
813 launcher.findViewById(R.id.apps_customize_pane);
814 if (host != null) {
815 // Center the all apps page indicator
816 int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
817 (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
818 pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
819 if (pageIndicator != null) {
Adam Cohen96bb7982014-07-07 11:58:56 -0700820 LinearLayout.LayoutParams lllp = (LinearLayout.LayoutParams) pageIndicator.getLayoutParams();
821 lllp.width = LayoutParams.WRAP_CONTENT;
822 lllp.height = pageIndicatorHeight;
823 pageIndicator.setLayoutParams(lllp);
Winson Chungb3800242013-10-24 11:01:54 -0700824 }
825
826 AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
827 host.findViewById(R.id.apps_customize_pane_content);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700828
829 FrameLayout fakePageContainer = (FrameLayout)
830 host.findViewById(R.id.fake_page_container);
831 FrameLayout fakePage = (FrameLayout) host.findViewById(R.id.fake_page);
832
Winson Chungb3800242013-10-24 11:01:54 -0700833 padding = new Rect();
834 if (pagedView != null) {
835 // Constrain the dimensions of all apps so that it does not span the full width
836 int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
837 (2 * (allAppsNumCols + 1));
838 int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
839 (2 * (allAppsNumRows + 1));
840 paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
841 paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
842 int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
843 int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
Winson Chung495f44d2013-12-04 12:51:53 -0800844 // Only adjust the side paddings on landscape phones, or tablets
845 if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
Winson Chungb3800242013-10-24 11:01:54 -0700846 padding.left = padding.right = gridPaddingLR;
847 }
Adam Cohen9bfdb762014-07-21 17:44:06 -0700848
Winson Chungb3800242013-10-24 11:01:54 -0700849 // The icons are centered, so we can't just offset by the page indicator height
850 // because the empty space will actually be pageIndicatorHeight + paddingTB
851 padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700852
Winson Chungb3800242013-10-24 11:01:54 -0700853 pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700854 fakePage.setBackground(res.getDrawable(R.drawable.quantum_panel));
Adam Cohen96bb7982014-07-07 11:58:56 -0700855
856 // Horizontal padding for the whole paged view
Adam Cohen9bfdb762014-07-21 17:44:06 -0700857 int pagedFixedViewPadding =
Adam Cohen96bb7982014-07-07 11:58:56 -0700858 res.getDimensionPixelSize(R.dimen.apps_customize_horizontal_padding);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700859
860 padding.left += pagedFixedViewPadding;
861 padding.right += pagedFixedViewPadding;
862
863 pagedView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
864 fakePageContainer.setPadding(padding.left, padding.top, padding.right, padding.bottom);
865
Winson Chungb3800242013-10-24 11:01:54 -0700866 }
867 }
868
869 // Layout the Overview Mode
Jorim Jaggid017f882014-01-14 17:08:48 -0800870 ViewGroup overviewMode = launcher.getOverviewPanel();
Winson Chungb3800242013-10-24 11:01:54 -0700871 if (overviewMode != null) {
872 Rect r = getOverviewModeButtonBarRect();
873 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
874 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
Jorim Jaggid017f882014-01-14 17:08:48 -0800875 lp.width = Math.min(availableWidthPx,
876 calculateOverviewModeWidth(getVisibleChildCount(overviewMode)));
Winson Chungb3800242013-10-24 11:01:54 -0700877 lp.height = r.height();
878 overviewMode.setLayoutParams(lp);
879 }
880 }
881}