blob: d6aadcee1e9655e52af0146ef95c9a17400acdec [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;
Sunny Goyal8dfe2da2014-10-24 16:26:52 -070036import android.view.ViewGroup.MarginLayoutParams;
Winson Chungb3800242013-10-24 11:01:54 -070037import android.view.WindowManager;
38import android.widget.FrameLayout;
Adam Cohen24ce0b32014-01-14 16:18:14 -080039import android.widget.LinearLayout;
Winson Chungb3800242013-10-24 11:01:54 -070040
41import java.util.ArrayList;
42import java.util.Collections;
43import java.util.Comparator;
44
45
46class DeviceProfileQuery {
Winson Chungbe876472014-05-14 14:15:20 -070047 DeviceProfile profile;
Winson Chungb3800242013-10-24 11:01:54 -070048 float widthDps;
49 float heightDps;
50 float value;
51 PointF dimens;
52
Winson Chungbe876472014-05-14 14:15:20 -070053 DeviceProfileQuery(DeviceProfile p, float v) {
54 widthDps = p.minWidthDps;
55 heightDps = p.minHeightDps;
Winson Chungb3800242013-10-24 11:01:54 -070056 value = v;
Winson Chungbe876472014-05-14 14:15:20 -070057 dimens = new PointF(widthDps, heightDps);
58 profile = p;
Winson Chungb3800242013-10-24 11:01:54 -070059 }
60}
61
62public class DeviceProfile {
63 public static interface DeviceProfileCallbacks {
64 public void onAvailableSizeChanged(DeviceProfile grid);
65 }
66
67 String name;
68 float minWidthDps;
69 float minHeightDps;
70 float numRows;
71 float numColumns;
72 float numHotseatIcons;
Adam Cohen4ae96ce2014-08-29 15:05:48 -070073 float iconSize;
Winson Chungb3800242013-10-24 11:01:54 -070074 private float iconTextSize;
75 private int iconDrawablePaddingOriginalPx;
76 private float hotseatIconSize;
77
Winson Chungbe876472014-05-14 14:15:20 -070078 int defaultLayoutId;
Winson Chungbe876472014-05-14 14:15:20 -070079
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;
Winson Chungb3800242013-10-24 11:01:54 -0700125 int searchBarSpaceHeightPx;
Winson Chungb3800242013-10-24 11:01:54 -0700126 int pageIndicatorHeightPx;
Adam Cohen63f1ec02014-08-12 09:23:13 -0700127 int allAppsButtonVisualSize;
Winson Chungb3800242013-10-24 11:01:54 -0700128
Winson Chung59a488a2013-12-10 12:32:14 -0800129 float dragViewScale;
130
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700131 int allAppsShortEdgeCount = -1;
132 int allAppsLongEdgeCount = -1;
133
Winson Chungb3800242013-10-24 11:01:54 -0700134 private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
135
136 DeviceProfile(String n, float w, float h, float r, float c,
Adam Cohencee8c662014-10-16 09:49:52 -0700137 float is, float its, float hs, float his, int dlId) {
Winson Chungb3800242013-10-24 11:01:54 -0700138 // Ensure that we have an odd number of hotseat items (since we need to place all apps)
Nilesh Agrawal16f3ea82014-01-09 17:14:01 -0800139 if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) {
Winson Chungb3800242013-10-24 11:01:54 -0700140 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
141 }
142
143 name = n;
144 minWidthDps = w;
145 minHeightDps = h;
146 numRows = r;
147 numColumns = c;
148 iconSize = is;
149 iconTextSize = its;
150 numHotseatIcons = hs;
151 hotseatIconSize = his;
Winson Chungbe876472014-05-14 14:15:20 -0700152 defaultLayoutId = dlId;
Winson Chungb3800242013-10-24 11:01:54 -0700153 }
154
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700155 DeviceProfile() {
156 }
157
Winson Chungb3800242013-10-24 11:01:54 -0700158 DeviceProfile(Context context,
159 ArrayList<DeviceProfile> profiles,
160 float minWidth, float minHeight,
161 int wPx, int hPx,
162 int awPx, int ahPx,
163 Resources res) {
164 DisplayMetrics dm = res.getDisplayMetrics();
165 ArrayList<DeviceProfileQuery> points =
166 new ArrayList<DeviceProfileQuery>();
167 transposeLayoutWithOrientation =
168 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
169 minWidthDps = minWidth;
170 minHeightDps = minHeight;
171
172 ComponentName cn = new ComponentName(context.getPackageName(),
173 this.getClass().getName());
174 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
175 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
176 desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
177 pageIndicatorHeightPx =
178 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
179 defaultPageSpacingPx =
180 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
181 allAppsCellPaddingPx =
182 res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
183 overviewModeMinIconZoneHeightPx =
184 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
185 overviewModeMaxIconZoneHeightPx =
186 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
Jorim Jaggid017f882014-01-14 17:08:48 -0800187 overviewModeBarItemWidthPx =
188 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
189 overviewModeBarSpacerWidthPx =
190 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
Winson Chungb3800242013-10-24 11:01:54 -0700191 overviewModeIconZoneRatio =
192 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
193 overviewModeScaleFactor =
194 res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
195
Winson Chungbe876472014-05-14 14:15:20 -0700196 // Find the closes profile given the width/height
Winson Chungb3800242013-10-24 11:01:54 -0700197 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700198 points.add(new DeviceProfileQuery(p, 0f));
Winson Chungb3800242013-10-24 11:01:54 -0700199 }
Winson Chungbe876472014-05-14 14:15:20 -0700200 DeviceProfile closestProfile = findClosestDeviceProfile(minWidth, minHeight, points);
201
202 // Snap to the closest row count
203 numRows = closestProfile.numRows;
204
205 // Snap to the closest column count
206 numColumns = closestProfile.numColumns;
207
208 // Snap to the closest hotseat size
209 numHotseatIcons = closestProfile.numHotseatIcons;
Winson Chungb3800242013-10-24 11:01:54 -0700210 hotseatAllAppsRank = (int) (numHotseatIcons / 2);
211
Winson Chungbe876472014-05-14 14:15:20 -0700212 // Snap to the closest default layout id
213 defaultLayoutId = closestProfile.defaultLayoutId;
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);
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700221
Winson Chungb3800242013-10-24 11:01:54 -0700222 // AllApps uses the original non-scaled icon size
223 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
224
225 // Interpolate the icon text size
226 points.clear();
227 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700228 points.add(new DeviceProfileQuery(p, p.iconTextSize));
Winson Chungb3800242013-10-24 11:01:54 -0700229 }
230 iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
231 iconDrawablePaddingOriginalPx =
232 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
233 // AllApps uses the original non-scaled icon text size
234 allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
235
236 // Interpolate the hotseat icon size
237 points.clear();
238 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700239 points.add(new DeviceProfileQuery(p, p.hotseatIconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700240 }
241 // Hotseat
242 hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
243
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700244 // If the partner customization apk contains any grid overrides, apply them
245 applyPartnerDeviceProfileOverrides(context, dm);
246
Winson Chungb3800242013-10-24 11:01:54 -0700247 // Calculate the remaining vars
248 updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
249 updateAvailableDimensions(context);
Adam Cohen63f1ec02014-08-12 09:23:13 -0700250 computeAllAppsButtonSize(context);
251 }
252
253 /**
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700254 * Apply any Partner customization grid overrides.
255 *
256 * Currently we support: all apps row / column count.
257 */
258 private void applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm) {
259 Partner p = Partner.get(ctx.getPackageManager());
260 if (p != null) {
261 DeviceProfile partnerDp = p.getDeviceProfileOverride(dm);
262 if (partnerDp != null) {
263 if (partnerDp.numRows > 0 && partnerDp.numColumns > 0) {
264 numRows = partnerDp.numRows;
265 numColumns = partnerDp.numColumns;
266 }
267 if (partnerDp.allAppsShortEdgeCount > 0 && partnerDp.allAppsLongEdgeCount > 0) {
268 allAppsShortEdgeCount = partnerDp.allAppsShortEdgeCount;
269 allAppsLongEdgeCount = partnerDp.allAppsLongEdgeCount;
270 }
271 if (partnerDp.iconSize > 0) {
272 iconSize = partnerDp.iconSize;
273 // AllApps uses the original non-scaled icon size
274 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
275 }
276 }
277 }
278 }
279
280 /**
Adam Cohen63f1ec02014-08-12 09:23:13 -0700281 * Determine the exact visual footprint of the all apps button, taking into account scaling
282 * and internal padding of the drawable.
283 */
284 private void computeAllAppsButtonSize(Context context) {
285 Resources res = context.getResources();
286 float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
287 LauncherAppState app = LauncherAppState.getInstance();
288 allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding));
Winson Chungb3800242013-10-24 11:01:54 -0700289 }
290
291 void addCallback(DeviceProfileCallbacks cb) {
292 mCallbacks.add(cb);
293 cb.onAvailableSizeChanged(this);
294 }
295 void removeCallback(DeviceProfileCallbacks cb) {
296 mCallbacks.remove(cb);
297 }
298
299 private int getDeviceOrientation(Context context) {
300 WindowManager windowManager = (WindowManager)
301 context.getSystemService(Context.WINDOW_SERVICE);
302 Resources resources = context.getResources();
303 DisplayMetrics dm = resources.getDisplayMetrics();
304 Configuration config = resources.getConfiguration();
305 int rotation = windowManager.getDefaultDisplay().getRotation();
306
307 boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
308 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
309 boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
310 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
311 if (isLandscape || isRotatedPortrait) {
312 return CellLayout.LANDSCAPE;
313 } else {
314 return CellLayout.PORTRAIT;
315 }
316 }
317
318 private void updateAvailableDimensions(Context context) {
319 WindowManager windowManager = (WindowManager)
320 context.getSystemService(Context.WINDOW_SERVICE);
321 Display display = windowManager.getDefaultDisplay();
322 Resources resources = context.getResources();
323 DisplayMetrics dm = resources.getDisplayMetrics();
324 Configuration config = resources.getConfiguration();
325
326 // There are three possible configurations that the dynamic grid accounts for, portrait,
327 // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
328 // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
329 // the height is the smallest height (either with the nav bar at the bottom or to the
330 // side) and otherwise, the height is simply the largest possible height for a portrait
331 // device.
332 Point size = new Point();
333 Point smallestSize = new Point();
334 Point largestSize = new Point();
335 display.getSize(size);
336 display.getCurrentSizeRange(smallestSize, largestSize);
337 availableWidthPx = size.x;
338 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
339 availableHeightPx = smallestSize.y;
340 } else {
341 availableHeightPx = largestSize.y;
342 }
343
344 // Check to see if the icons fit in the new available height. If not, then we need to
345 // shrink the icon size.
Winson Chungb3800242013-10-24 11:01:54 -0700346 float scale = 1f;
347 int drawablePadding = iconDrawablePaddingOriginalPx;
348 updateIconSize(1f, drawablePadding, resources, dm);
349 float usedHeight = (cellHeightPx * numRows);
Winson Chung59a488a2013-12-10 12:32:14 -0800350
351 Rect workspacePadding = getWorkspacePadding();
Winson Chungb3800242013-10-24 11:01:54 -0700352 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
353 if (usedHeight > maxHeight) {
354 scale = maxHeight / usedHeight;
355 drawablePadding = 0;
356 }
357 updateIconSize(scale, drawablePadding, resources, dm);
358
359 // Make the callbacks
360 for (DeviceProfileCallbacks cb : mCallbacks) {
361 cb.onAvailableSizeChanged(this);
362 }
363 }
364
365 private void updateIconSize(float scale, int drawablePadding, Resources resources,
366 DisplayMetrics dm) {
367 iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
368 iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
369 iconDrawablePaddingPx = drawablePadding;
370 hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
371
372 // Search Bar
Sunny Goyal594d76d2014-11-06 10:12:54 -0800373 searchBarSpaceWidthPx = Math.min(widthPx,
374 resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
375 searchBarSpaceHeightPx = getSearchBarTopOffset()
376 + resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
Winson Chungb3800242013-10-24 11:01:54 -0700377
378 // Calculate the actual text height
379 Paint textPaint = new Paint();
380 textPaint.setTextSize(iconTextSizePx);
381 FontMetrics fm = textPaint.getFontMetrics();
382 cellWidthPx = iconSizePx;
383 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
Winson Chung59a488a2013-12-10 12:32:14 -0800384 final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale);
385 dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
Winson Chungb3800242013-10-24 11:01:54 -0700386
387 // Hotseat
388 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
389 hotseatCellWidthPx = iconSizePx;
390 hotseatCellHeightPx = iconSizePx;
391
392 // Folder
393 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
394 folderCellHeightPx = cellHeightPx + edgeMarginPx;
395 folderBackgroundOffset = -edgeMarginPx;
396 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
397
398 // All Apps
Winson Chungb3800242013-10-24 11:01:54 -0700399 allAppsCellWidthPx = allAppsIconSizePx;
400 allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
401 int maxLongEdgeCellCount =
402 resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
403 int maxShortEdgeCellCount =
404 resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
405 int minEdgeCellCount =
406 resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
407 int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
408 int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
409
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700410 if (allAppsShortEdgeCount > 0 && allAppsLongEdgeCount > 0) {
411 allAppsNumRows = isLandscape ? allAppsShortEdgeCount : allAppsLongEdgeCount;
412 allAppsNumCols = isLandscape ? allAppsLongEdgeCount : allAppsShortEdgeCount;
413 } else {
414 allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
415 (allAppsCellHeightPx + allAppsCellPaddingPx);
416 allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
417 allAppsNumCols = (availableWidthPx) /
418 (allAppsCellWidthPx + allAppsCellPaddingPx);
419 allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
420 }
Winson Chungb3800242013-10-24 11:01:54 -0700421 }
422
423 void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
424 int awPx, int ahPx) {
Winson Chung42b3c062013-12-04 12:09:59 -0800425 Configuration configuration = resources.getConfiguration();
426 isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
Winson Chungb3800242013-10-24 11:01:54 -0700427 isTablet = resources.getBoolean(R.bool.is_tablet);
428 isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
Winson Chung6033ceb2014-02-05 12:37:42 -0800429 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
430 isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
431 } else {
432 isLayoutRtl = false;
433 }
Winson Chungb3800242013-10-24 11:01:54 -0700434 widthPx = wPx;
435 heightPx = hPx;
436 availableWidthPx = awPx;
437 availableHeightPx = ahPx;
438
439 updateAvailableDimensions(context);
440 }
441
442 private float dist(PointF p0, PointF p1) {
443 return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
444 (p1.y-p0.y)*(p1.y-p0.y));
445 }
446
447 private float weight(PointF a, PointF b,
448 float pow) {
449 float d = dist(a, b);
450 if (d == 0f) {
451 return Float.POSITIVE_INFINITY;
452 }
453 return (float) (1f / Math.pow(d, pow));
454 }
455
Winson Chungbe876472014-05-14 14:15:20 -0700456 /** Returns the closest device profile given the width and height and a list of profiles */
457 private DeviceProfile findClosestDeviceProfile(float width, float height,
458 ArrayList<DeviceProfileQuery> points) {
459 return findClosestDeviceProfiles(width, height, points).get(0).profile;
460 }
461
462 /** Returns the closest device profiles ordered by closeness to the specified width and height */
463 private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height,
464 ArrayList<DeviceProfileQuery> points) {
465 final PointF xy = new PointF(width, height);
466
467 // Sort the profiles by their closeness to the dimensions
468 ArrayList<DeviceProfileQuery> pointsByNearness = points;
469 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
470 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
471 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
472 }
473 });
474
475 return pointsByNearness;
476 }
477
Winson Chungb3800242013-10-24 11:01:54 -0700478 private float invDistWeightedInterpolate(float width, float height,
479 ArrayList<DeviceProfileQuery> points) {
480 float sum = 0;
481 float weights = 0;
482 float pow = 5;
483 float kNearestNeighbors = 3;
484 final PointF xy = new PointF(width, height);
485
Winson Chungbe876472014-05-14 14:15:20 -0700486 ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height,
487 points);
Winson Chungb3800242013-10-24 11:01:54 -0700488
489 for (int i = 0; i < pointsByNearness.size(); ++i) {
490 DeviceProfileQuery p = pointsByNearness.get(i);
491 if (i < kNearestNeighbors) {
492 float w = weight(xy, p.dimens, pow);
493 if (w == Float.POSITIVE_INFINITY) {
494 return p.value;
495 }
496 weights += w;
497 }
498 }
499
500 for (int i = 0; i < pointsByNearness.size(); ++i) {
501 DeviceProfileQuery p = pointsByNearness.get(i);
502 if (i < kNearestNeighbors) {
503 float w = weight(xy, p.dimens, pow);
504 sum += w * p.value / weights;
505 }
506 }
507
508 return sum;
509 }
510
Winson Chung69e04ea2013-12-02 14:43:44 -0800511 /** Returns the search bar top offset */
512 int getSearchBarTopOffset() {
513 if (isTablet() && !isVerticalBarLayout()) {
514 return 4 * edgeMarginPx;
515 } else {
516 return 2 * edgeMarginPx;
517 }
518 }
519
Winson Chungb3800242013-10-24 11:01:54 -0700520 /** Returns the search bar bounds in the current orientation */
521 Rect getSearchBarBounds() {
522 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
523 }
524 /** Returns the search bar bounds in the specified orientation */
525 Rect getSearchBarBounds(int orientation) {
526 Rect bounds = new Rect();
527 if (orientation == CellLayout.LANDSCAPE &&
528 transposeLayoutWithOrientation) {
Winson Chung42b3c062013-12-04 12:09:59 -0800529 if (isLayoutRtl) {
530 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
531 availableWidthPx, availableHeightPx - edgeMarginPx);
532 } else {
533 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
534 availableHeightPx - edgeMarginPx);
535 }
Winson Chungb3800242013-10-24 11:01:54 -0700536 } else {
537 if (isTablet()) {
538 // Pad the left and right of the workspace to ensure consistent spacing
539 // between all icons
540 int width = (orientation == CellLayout.LANDSCAPE)
541 ? Math.max(widthPx, heightPx)
542 : Math.min(widthPx, heightPx);
543 // XXX: If the icon size changes across orientations, we will have to take
544 // that into account here too.
545 int gap = (int) ((width - 2 * edgeMarginPx -
546 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
Winson Chung2cb24712013-12-02 15:00:39 -0800547 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
548 availableWidthPx - (edgeMarginPx + gap),
Winson Chungb3800242013-10-24 11:01:54 -0700549 searchBarSpaceHeightPx);
550 } else {
Winson Chung2cb24712013-12-02 15:00:39 -0800551 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
552 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700553 availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
554 defaultWidgetPadding.right), searchBarSpaceHeightPx);
555 }
556 }
557 return bounds;
558 }
559
Winson Chunga6945242014-01-08 14:04:34 -0800560 /** Returns the bounds of the workspace page indicators. */
561 Rect getWorkspacePageIndicatorBounds(Rect insets) {
562 Rect workspacePadding = getWorkspacePadding();
Winson Chung205cd772014-01-15 14:31:59 -0800563 if (isLandscape && transposeLayoutWithOrientation) {
564 if (isLayoutRtl) {
565 return new Rect(workspacePadding.left, workspacePadding.top,
566 workspacePadding.left + pageIndicatorHeightPx,
567 heightPx - workspacePadding.bottom - insets.bottom);
568 } else {
569 int pageIndicatorLeft = widthPx - workspacePadding.right;
570 return new Rect(pageIndicatorLeft, workspacePadding.top,
571 pageIndicatorLeft + pageIndicatorHeightPx,
572 heightPx - workspacePadding.bottom - insets.bottom);
573 }
574 } else {
575 int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom;
576 return new Rect(workspacePadding.left, pageIndicatorTop,
577 widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx);
578 }
Winson Chunga6945242014-01-08 14:04:34 -0800579 }
580
Winson Chungb3800242013-10-24 11:01:54 -0700581 /** Returns the workspace padding in the specified orientation */
582 Rect getWorkspacePadding() {
583 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
584 }
585 Rect getWorkspacePadding(int orientation) {
586 Rect searchBarBounds = getSearchBarBounds(orientation);
587 Rect padding = new Rect();
588 if (orientation == CellLayout.LANDSCAPE &&
589 transposeLayoutWithOrientation) {
590 // Pad the left and right of the workspace with search/hotseat bar sizes
Winson Chung42b3c062013-12-04 12:09:59 -0800591 if (isLayoutRtl) {
592 padding.set(hotseatBarHeightPx, edgeMarginPx,
593 searchBarBounds.width(), edgeMarginPx);
594 } else {
595 padding.set(searchBarBounds.width(), edgeMarginPx,
596 hotseatBarHeightPx, edgeMarginPx);
597 }
Winson Chungb3800242013-10-24 11:01:54 -0700598 } else {
599 if (isTablet()) {
600 // Pad the left and right of the workspace to ensure consistent spacing
601 // between all icons
Winson Chung59a488a2013-12-10 12:32:14 -0800602 float gapScale = 1f + (dragViewScale - 1f) / 2f;
Winson Chungb3800242013-10-24 11:01:54 -0700603 int width = (orientation == CellLayout.LANDSCAPE)
604 ? Math.max(widthPx, heightPx)
605 : Math.min(widthPx, heightPx);
Winson Chung59a488a2013-12-10 12:32:14 -0800606 int height = (orientation != CellLayout.LANDSCAPE)
607 ? Math.max(widthPx, heightPx)
608 : Math.min(widthPx, heightPx);
609 int paddingTop = searchBarBounds.bottom;
610 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
611 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
612 (numColumns * gapScale * cellWidthPx)));
613 int availableHeight = Math.max(0, height - paddingTop - paddingBottom
614 - (int) (2 * numRows * cellHeightPx));
615 padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
616 availableWidth / 2, paddingBottom + availableHeight / 2);
Winson Chungb3800242013-10-24 11:01:54 -0700617 } else {
618 // Pad the top and bottom of the workspace with search/hotseat bar sizes
619 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
620 searchBarBounds.bottom,
621 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
622 hotseatBarHeightPx + pageIndicatorHeightPx);
623 }
624 }
625 return padding;
626 }
627
628 int getWorkspacePageSpacing(int orientation) {
Winson Chung59a488a2013-12-10 12:32:14 -0800629 if ((orientation == CellLayout.LANDSCAPE &&
630 transposeLayoutWithOrientation) || isLargeTablet()) {
Winson Chungb3800242013-10-24 11:01:54 -0700631 // In landscape mode the page spacing is set to the default.
632 return defaultPageSpacingPx;
633 } else {
634 // In portrait, we want the pages spaced such that there is no
635 // overhang of the previous / next page into the current page viewport.
636 // We assume symmetrical padding in portrait mode.
Adam Cohenefb31e32014-01-16 16:07:50 -0800637 return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
Winson Chungb3800242013-10-24 11:01:54 -0700638 }
639 }
640
641 Rect getOverviewModeButtonBarRect() {
642 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
643 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
644 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
645 return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
646 }
647
648 float getOverviewModeScale() {
649 Rect workspacePadding = getWorkspacePadding();
650 Rect overviewBar = getOverviewModeButtonBarRect();
651 int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
652 return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
653 }
654
655 // The rect returned will be extended to below the system ui that covers the workspace
656 Rect getHotseatRect() {
657 if (isVerticalBarLayout()) {
658 return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
659 Integer.MAX_VALUE, availableHeightPx);
660 } else {
661 return new Rect(0, availableHeightPx - hotseatBarHeightPx,
662 availableWidthPx, Integer.MAX_VALUE);
663 }
664 }
665
666 int calculateCellWidth(int width, int countX) {
667 return width / countX;
668 }
669 int calculateCellHeight(int height, int countY) {
670 return height / countY;
671 }
672
673 boolean isPhone() {
674 return !isTablet && !isLargeTablet;
675 }
676 boolean isTablet() {
677 return isTablet;
678 }
679 boolean isLargeTablet() {
680 return isLargeTablet;
681 }
682
683 boolean isVerticalBarLayout() {
684 return isLandscape && transposeLayoutWithOrientation;
685 }
686
687 boolean shouldFadeAdjacentWorkspaceScreens() {
688 return isVerticalBarLayout() || isLargeTablet();
689 }
690
Jorim Jaggid017f882014-01-14 17:08:48 -0800691 int getVisibleChildCount(ViewGroup parent) {
692 int visibleChildren = 0;
693 for (int i = 0; i < parent.getChildCount(); i++) {
694 if (parent.getChildAt(i).getVisibility() != View.GONE) {
695 visibleChildren++;
696 }
697 }
698 return visibleChildren;
699 }
700
Winson Chungb3800242013-10-24 11:01:54 -0700701 public void layout(Launcher launcher) {
702 FrameLayout.LayoutParams lp;
703 Resources res = launcher.getResources();
704 boolean hasVerticalBarLayout = isVerticalBarLayout();
705
706 // Layout the search bar space
707 View searchBar = launcher.getSearchBar();
708 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
709 if (hasVerticalBarLayout) {
Winson Chung69e04ea2013-12-02 14:43:44 -0800710 // Vertical search bar space
Winson Chungb3800242013-10-24 11:01:54 -0700711 lp.gravity = Gravity.TOP | Gravity.LEFT;
712 lp.width = searchBarSpaceHeightPx;
Adam Cohen24ce0b32014-01-14 16:18:14 -0800713 lp.height = LayoutParams.WRAP_CONTENT;
Adam Cohen24ce0b32014-01-14 16:18:14 -0800714
715 LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
716 targets.setOrientation(LinearLayout.VERTICAL);
Winson Chungb3800242013-10-24 11:01:54 -0700717 } else {
Winson Chung69e04ea2013-12-02 14:43:44 -0800718 // Horizontal search bar space
Winson Chungb3800242013-10-24 11:01:54 -0700719 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
720 lp.width = searchBarSpaceWidthPx;
721 lp.height = searchBarSpaceHeightPx;
Winson Chungb3800242013-10-24 11:01:54 -0700722 }
723 searchBar.setLayoutParams(lp);
724
Winson Chungb3800242013-10-24 11:01:54 -0700725 // Layout the workspace
726 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
727 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
728 lp.gravity = Gravity.CENTER;
729 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
730 Rect padding = getWorkspacePadding(orientation);
731 workspace.setLayoutParams(lp);
732 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
733 workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
734
735 // Layout the hotseat
736 View hotseat = launcher.findViewById(R.id.hotseat);
737 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
738 if (hasVerticalBarLayout) {
739 // Vertical hotseat
Winson Chung42b3c062013-12-04 12:09:59 -0800740 lp.gravity = Gravity.END;
Winson Chungb3800242013-10-24 11:01:54 -0700741 lp.width = hotseatBarHeightPx;
742 lp.height = LayoutParams.MATCH_PARENT;
743 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
744 } else if (isTablet()) {
Winson Chung59a488a2013-12-10 12:32:14 -0800745 // Pad the hotseat with the workspace padding calculated above
Winson Chungb3800242013-10-24 11:01:54 -0700746 lp.gravity = Gravity.BOTTOM;
747 lp.width = LayoutParams.MATCH_PARENT;
748 lp.height = hotseatBarHeightPx;
Winson Chung59a488a2013-12-10 12:32:14 -0800749 hotseat.setPadding(edgeMarginPx + padding.left, 0,
750 edgeMarginPx + padding.right,
Winson Chungb3800242013-10-24 11:01:54 -0700751 2 * edgeMarginPx);
752 } else {
753 // For phones, layout the hotseat without any bottom margin
754 // to ensure that we have space for the folders
755 lp.gravity = Gravity.BOTTOM;
756 lp.width = LayoutParams.MATCH_PARENT;
757 lp.height = hotseatBarHeightPx;
758 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
759 2 * edgeMarginPx, 0);
760 }
761 hotseat.setLayoutParams(lp);
762
763 // Layout the page indicators
764 View pageIndicator = launcher.findViewById(R.id.page_indicator);
765 if (pageIndicator != null) {
766 if (hasVerticalBarLayout) {
767 // Hide the page indicators when we have vertical search/hotseat
768 pageIndicator.setVisibility(View.GONE);
769 } else {
770 // Put the page indicators above the hotseat
771 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
772 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
773 lp.width = LayoutParams.WRAP_CONTENT;
774 lp.height = LayoutParams.WRAP_CONTENT;
775 lp.bottomMargin = hotseatBarHeightPx;
776 pageIndicator.setLayoutParams(lp);
777 }
778 }
779
780 // Layout AllApps
781 AppsCustomizeTabHost host = (AppsCustomizeTabHost)
782 launcher.findViewById(R.id.apps_customize_pane);
783 if (host != null) {
784 // Center the all apps page indicator
785 int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
786 (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
787 pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
788 if (pageIndicator != null) {
Adam Cohen96bb7982014-07-07 11:58:56 -0700789 LinearLayout.LayoutParams lllp = (LinearLayout.LayoutParams) pageIndicator.getLayoutParams();
790 lllp.width = LayoutParams.WRAP_CONTENT;
791 lllp.height = pageIndicatorHeight;
792 pageIndicator.setLayoutParams(lllp);
Winson Chungb3800242013-10-24 11:01:54 -0700793 }
794
795 AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
796 host.findViewById(R.id.apps_customize_pane_content);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700797
798 FrameLayout fakePageContainer = (FrameLayout)
799 host.findViewById(R.id.fake_page_container);
800 FrameLayout fakePage = (FrameLayout) host.findViewById(R.id.fake_page);
801
Winson Chungb3800242013-10-24 11:01:54 -0700802 padding = new Rect();
803 if (pagedView != null) {
804 // Constrain the dimensions of all apps so that it does not span the full width
805 int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
806 (2 * (allAppsNumCols + 1));
807 int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
808 (2 * (allAppsNumRows + 1));
809 paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
810 paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
811 int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
812 int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
Winson Chung495f44d2013-12-04 12:51:53 -0800813 // Only adjust the side paddings on landscape phones, or tablets
814 if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
Winson Chungb3800242013-10-24 11:01:54 -0700815 padding.left = padding.right = gridPaddingLR;
816 }
Adam Cohen9bfdb762014-07-21 17:44:06 -0700817
Winson Chungb3800242013-10-24 11:01:54 -0700818 // The icons are centered, so we can't just offset by the page indicator height
819 // because the empty space will actually be pageIndicatorHeight + paddingTB
820 padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700821
Winson Chungb3800242013-10-24 11:01:54 -0700822 pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700823 fakePage.setBackground(res.getDrawable(R.drawable.quantum_panel));
Adam Cohen96bb7982014-07-07 11:58:56 -0700824
825 // Horizontal padding for the whole paged view
Adam Cohen9bfdb762014-07-21 17:44:06 -0700826 int pagedFixedViewPadding =
Adam Cohen96bb7982014-07-07 11:58:56 -0700827 res.getDimensionPixelSize(R.dimen.apps_customize_horizontal_padding);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700828
829 padding.left += pagedFixedViewPadding;
830 padding.right += pagedFixedViewPadding;
831
832 pagedView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
833 fakePageContainer.setPadding(padding.left, padding.top, padding.right, padding.bottom);
834
Winson Chungb3800242013-10-24 11:01:54 -0700835 }
836 }
837
838 // Layout the Overview Mode
Jorim Jaggid017f882014-01-14 17:08:48 -0800839 ViewGroup overviewMode = launcher.getOverviewPanel();
Winson Chungb3800242013-10-24 11:01:54 -0700840 if (overviewMode != null) {
841 Rect r = getOverviewModeButtonBarRect();
842 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
843 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
Sunny Goyal8dfe2da2014-10-24 16:26:52 -0700844
845 int visibleChildCount = getVisibleChildCount(overviewMode);
846 int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
847 int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
848
849 lp.width = Math.min(availableWidthPx, maxWidth);
Winson Chungb3800242013-10-24 11:01:54 -0700850 lp.height = r.height();
851 overviewMode.setLayoutParams(lp);
Sunny Goyal8dfe2da2014-10-24 16:26:52 -0700852
853 if (lp.width > totalItemWidth && visibleChildCount > 1) {
854 // We have enough space. Lets add some margin too.
855 int margin = (lp.width - totalItemWidth) / (visibleChildCount-1);
856 View lastChild = null;
857
858 // Set margin of all visible children except the last visible child
859 for (int i = 0; i < visibleChildCount; i++) {
860 if (lastChild != null) {
861 MarginLayoutParams clp = (MarginLayoutParams) lastChild.getLayoutParams();
862 if (isLayoutRtl) {
863 clp.leftMargin = margin;
864 } else {
865 clp.rightMargin = margin;
866 }
867 lastChild.setLayoutParams(clp);
868 lastChild = null;
869 }
870 View thisChild = overviewMode.getChildAt(i);
871 if (thisChild.getVisibility() != View.GONE) {
872 lastChild = thisChild;
873 }
874 }
875 }
Winson Chungb3800242013-10-24 11:01:54 -0700876 }
877 }
878}