blob: b5bb55ca7f0933048fc9842ba372486575f925b5 [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;
Adam Cohen59400422014-03-05 18:07:04 -080070 public float numRows;
71 public float numColumns;
Winson Chungb3800242013-10-24 11:01:54 -070072 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;
Adam Cohen59400422014-03-05 18:07:04 -080087 public int edgeMarginPx;
Winson Chungb3800242013-10-24 11:01:54 -070088 Rect defaultWidgetPadding;
89
90 int widthPx;
91 int heightPx;
Adam Cohen59400422014-03-05 18:07:04 -080092 public int availableWidthPx;
93 public int availableHeightPx;
Winson Chungb3800242013-10-24 11:01:54 -070094 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
Adam Cohen59400422014-03-05 18:07:04 -0800103 public int cellWidthPx;
104 public int cellHeightPx;
105
Winson Chungb3800242013-10-24 11:01:54 -0700106 int iconSizePx;
107 int iconTextSizePx;
108 int iconDrawablePaddingPx;
Winson Chungb3800242013-10-24 11:01:54 -0700109 int allAppsIconSizePx;
110 int allAppsIconTextSizePx;
111 int allAppsCellWidthPx;
112 int allAppsCellHeightPx;
113 int allAppsCellPaddingPx;
114 int folderBackgroundOffset;
115 int folderIconSizePx;
116 int folderCellWidthPx;
117 int folderCellHeightPx;
118 int hotseatCellWidthPx;
119 int hotseatCellHeightPx;
120 int hotseatIconSizePx;
121 int hotseatBarHeightPx;
122 int hotseatAllAppsRank;
123 int allAppsNumRows;
124 int allAppsNumCols;
Winson Chungb745afb2015-03-02 11:51:23 -0800125 // TODO(winsonc): to be used with the grid layout
126 int allAppsRowsSize;
Winson Chungb3800242013-10-24 11:01:54 -0700127 int searchBarSpaceWidthPx;
Winson Chungb3800242013-10-24 11:01:54 -0700128 int searchBarSpaceHeightPx;
Winson Chungb3800242013-10-24 11:01:54 -0700129 int pageIndicatorHeightPx;
Adam Cohen63f1ec02014-08-12 09:23:13 -0700130 int allAppsButtonVisualSize;
Winson Chungb3800242013-10-24 11:01:54 -0700131
Winson Chung59a488a2013-12-10 12:32:14 -0800132 float dragViewScale;
133
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700134 int allAppsShortEdgeCount = -1;
135 int allAppsLongEdgeCount = -1;
136
Winson Chungb3800242013-10-24 11:01:54 -0700137 private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
138
139 DeviceProfile(String n, float w, float h, float r, float c,
Adam Cohencee8c662014-10-16 09:49:52 -0700140 float is, float its, float hs, float his, int dlId) {
Winson Chungb3800242013-10-24 11:01:54 -0700141 // Ensure that we have an odd number of hotseat items (since we need to place all apps)
Sunny Goyalc9acdd52015-02-26 12:34:42 -0800142 if (hs % 2 == 0) {
Winson Chungb3800242013-10-24 11:01:54 -0700143 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
144 }
145
146 name = n;
147 minWidthDps = w;
148 minHeightDps = h;
149 numRows = r;
150 numColumns = c;
151 iconSize = is;
152 iconTextSize = its;
153 numHotseatIcons = hs;
154 hotseatIconSize = his;
Winson Chungbe876472014-05-14 14:15:20 -0700155 defaultLayoutId = dlId;
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
Winson Chungb3800242013-10-24 11:01:54 -0700218 // Interpolate the icon size
219 points.clear();
220 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700221 points.add(new DeviceProfileQuery(p, p.iconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700222 }
223 iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700224
Winson Chungb3800242013-10-24 11:01:54 -0700225 // AllApps uses the original non-scaled icon size
226 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
227
228 // Interpolate the icon text size
229 points.clear();
230 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700231 points.add(new DeviceProfileQuery(p, p.iconTextSize));
Winson Chungb3800242013-10-24 11:01:54 -0700232 }
233 iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
234 iconDrawablePaddingOriginalPx =
235 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
236 // AllApps uses the original non-scaled icon text size
237 allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
238
239 // Interpolate the hotseat icon size
240 points.clear();
241 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700242 points.add(new DeviceProfileQuery(p, p.hotseatIconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700243 }
244 // Hotseat
245 hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
246
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700247 // If the partner customization apk contains any grid overrides, apply them
248 applyPartnerDeviceProfileOverrides(context, dm);
249
Winson Chungb3800242013-10-24 11:01:54 -0700250 // Calculate the remaining vars
251 updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
252 updateAvailableDimensions(context);
Adam Cohen63f1ec02014-08-12 09:23:13 -0700253 computeAllAppsButtonSize(context);
254 }
255
256 /**
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700257 * Apply any Partner customization grid overrides.
258 *
259 * Currently we support: all apps row / column count.
260 */
261 private void applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm) {
262 Partner p = Partner.get(ctx.getPackageManager());
263 if (p != null) {
264 DeviceProfile partnerDp = p.getDeviceProfileOverride(dm);
265 if (partnerDp != null) {
266 if (partnerDp.numRows > 0 && partnerDp.numColumns > 0) {
267 numRows = partnerDp.numRows;
268 numColumns = partnerDp.numColumns;
269 }
270 if (partnerDp.allAppsShortEdgeCount > 0 && partnerDp.allAppsLongEdgeCount > 0) {
271 allAppsShortEdgeCount = partnerDp.allAppsShortEdgeCount;
272 allAppsLongEdgeCount = partnerDp.allAppsLongEdgeCount;
273 }
274 if (partnerDp.iconSize > 0) {
275 iconSize = partnerDp.iconSize;
276 // AllApps uses the original non-scaled icon size
277 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
278 }
279 }
280 }
281 }
282
283 /**
Adam Cohen63f1ec02014-08-12 09:23:13 -0700284 * Determine the exact visual footprint of the all apps button, taking into account scaling
285 * and internal padding of the drawable.
286 */
287 private void computeAllAppsButtonSize(Context context) {
288 Resources res = context.getResources();
289 float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
290 LauncherAppState app = LauncherAppState.getInstance();
291 allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding));
Winson Chungb3800242013-10-24 11:01:54 -0700292 }
293
294 void addCallback(DeviceProfileCallbacks cb) {
295 mCallbacks.add(cb);
296 cb.onAvailableSizeChanged(this);
297 }
298 void removeCallback(DeviceProfileCallbacks cb) {
299 mCallbacks.remove(cb);
300 }
301
302 private int getDeviceOrientation(Context context) {
303 WindowManager windowManager = (WindowManager)
304 context.getSystemService(Context.WINDOW_SERVICE);
305 Resources resources = context.getResources();
306 DisplayMetrics dm = resources.getDisplayMetrics();
307 Configuration config = resources.getConfiguration();
308 int rotation = windowManager.getDefaultDisplay().getRotation();
309
310 boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
311 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
312 boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
313 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
314 if (isLandscape || isRotatedPortrait) {
315 return CellLayout.LANDSCAPE;
316 } else {
317 return CellLayout.PORTRAIT;
318 }
319 }
320
321 private void updateAvailableDimensions(Context context) {
322 WindowManager windowManager = (WindowManager)
323 context.getSystemService(Context.WINDOW_SERVICE);
324 Display display = windowManager.getDefaultDisplay();
325 Resources resources = context.getResources();
326 DisplayMetrics dm = resources.getDisplayMetrics();
327 Configuration config = resources.getConfiguration();
328
329 // There are three possible configurations that the dynamic grid accounts for, portrait,
330 // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
331 // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
332 // the height is the smallest height (either with the nav bar at the bottom or to the
333 // side) and otherwise, the height is simply the largest possible height for a portrait
334 // device.
335 Point size = new Point();
336 Point smallestSize = new Point();
337 Point largestSize = new Point();
338 display.getSize(size);
339 display.getCurrentSizeRange(smallestSize, largestSize);
340 availableWidthPx = size.x;
341 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
342 availableHeightPx = smallestSize.y;
343 } else {
344 availableHeightPx = largestSize.y;
345 }
346
347 // Check to see if the icons fit in the new available height. If not, then we need to
348 // shrink the icon size.
Winson Chungb3800242013-10-24 11:01:54 -0700349 float scale = 1f;
350 int drawablePadding = iconDrawablePaddingOriginalPx;
351 updateIconSize(1f, drawablePadding, resources, dm);
352 float usedHeight = (cellHeightPx * numRows);
Winson Chung59a488a2013-12-10 12:32:14 -0800353
354 Rect workspacePadding = getWorkspacePadding();
Winson Chungb3800242013-10-24 11:01:54 -0700355 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
356 if (usedHeight > maxHeight) {
357 scale = maxHeight / usedHeight;
358 drawablePadding = 0;
359 }
360 updateIconSize(scale, drawablePadding, resources, dm);
361
362 // Make the callbacks
363 for (DeviceProfileCallbacks cb : mCallbacks) {
364 cb.onAvailableSizeChanged(this);
365 }
366 }
367
368 private void updateIconSize(float scale, int drawablePadding, Resources resources,
369 DisplayMetrics dm) {
370 iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
371 iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
372 iconDrawablePaddingPx = drawablePadding;
373 hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
374
375 // Search Bar
Sunny Goyal594d76d2014-11-06 10:12:54 -0800376 searchBarSpaceWidthPx = Math.min(widthPx,
377 resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
378 searchBarSpaceHeightPx = getSearchBarTopOffset()
379 + resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
Winson Chungb3800242013-10-24 11:01:54 -0700380
381 // Calculate the actual text height
382 Paint textPaint = new Paint();
383 textPaint.setTextSize(iconTextSizePx);
384 FontMetrics fm = textPaint.getFontMetrics();
385 cellWidthPx = iconSizePx;
386 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
Winson Chung59a488a2013-12-10 12:32:14 -0800387 final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale);
388 dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
Winson Chungb3800242013-10-24 11:01:54 -0700389
390 // Hotseat
391 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
392 hotseatCellWidthPx = iconSizePx;
393 hotseatCellHeightPx = iconSizePx;
394
395 // Folder
396 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
397 folderCellHeightPx = cellHeightPx + edgeMarginPx;
398 folderBackgroundOffset = -edgeMarginPx;
399 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
400
401 // All Apps
Winson Chungb3800242013-10-24 11:01:54 -0700402 allAppsCellWidthPx = allAppsIconSizePx;
403 allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
404 int maxLongEdgeCellCount =
405 resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
406 int maxShortEdgeCellCount =
407 resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
408 int minEdgeCellCount =
409 resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
410 int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
411 int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
412
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700413 if (allAppsShortEdgeCount > 0 && allAppsLongEdgeCount > 0) {
414 allAppsNumRows = isLandscape ? allAppsShortEdgeCount : allAppsLongEdgeCount;
415 allAppsNumCols = isLandscape ? allAppsLongEdgeCount : allAppsShortEdgeCount;
416 } else {
417 allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
418 (allAppsCellHeightPx + allAppsCellPaddingPx);
419 allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
420 allAppsNumCols = (availableWidthPx) /
421 (allAppsCellWidthPx + allAppsCellPaddingPx);
422 allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
423 }
Winson Chungb3800242013-10-24 11:01:54 -0700424 }
425
426 void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
427 int awPx, int ahPx) {
Winson Chung42b3c062013-12-04 12:09:59 -0800428 Configuration configuration = resources.getConfiguration();
429 isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
Winson Chungb3800242013-10-24 11:01:54 -0700430 isTablet = resources.getBoolean(R.bool.is_tablet);
431 isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
Winson Chung6033ceb2014-02-05 12:37:42 -0800432 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
433 isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
434 } else {
435 isLayoutRtl = false;
436 }
Winson Chungb3800242013-10-24 11:01:54 -0700437 widthPx = wPx;
438 heightPx = hPx;
439 availableWidthPx = awPx;
440 availableHeightPx = ahPx;
441
442 updateAvailableDimensions(context);
443 }
444
445 private float dist(PointF p0, PointF p1) {
446 return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
447 (p1.y-p0.y)*(p1.y-p0.y));
448 }
449
450 private float weight(PointF a, PointF b,
451 float pow) {
452 float d = dist(a, b);
453 if (d == 0f) {
454 return Float.POSITIVE_INFINITY;
455 }
456 return (float) (1f / Math.pow(d, pow));
457 }
458
Winson Chungbe876472014-05-14 14:15:20 -0700459 /** Returns the closest device profile given the width and height and a list of profiles */
460 private DeviceProfile findClosestDeviceProfile(float width, float height,
461 ArrayList<DeviceProfileQuery> points) {
462 return findClosestDeviceProfiles(width, height, points).get(0).profile;
463 }
464
465 /** Returns the closest device profiles ordered by closeness to the specified width and height */
466 private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height,
467 ArrayList<DeviceProfileQuery> points) {
468 final PointF xy = new PointF(width, height);
469
470 // Sort the profiles by their closeness to the dimensions
471 ArrayList<DeviceProfileQuery> pointsByNearness = points;
472 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
473 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
474 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
475 }
476 });
477
478 return pointsByNearness;
479 }
480
Winson Chungb3800242013-10-24 11:01:54 -0700481 private float invDistWeightedInterpolate(float width, float height,
482 ArrayList<DeviceProfileQuery> points) {
483 float sum = 0;
484 float weights = 0;
485 float pow = 5;
486 float kNearestNeighbors = 3;
487 final PointF xy = new PointF(width, height);
488
Winson Chungbe876472014-05-14 14:15:20 -0700489 ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height,
490 points);
Winson Chungb3800242013-10-24 11:01:54 -0700491
492 for (int i = 0; i < pointsByNearness.size(); ++i) {
493 DeviceProfileQuery p = pointsByNearness.get(i);
494 if (i < kNearestNeighbors) {
495 float w = weight(xy, p.dimens, pow);
496 if (w == Float.POSITIVE_INFINITY) {
497 return p.value;
498 }
499 weights += w;
500 }
501 }
502
503 for (int i = 0; i < pointsByNearness.size(); ++i) {
504 DeviceProfileQuery p = pointsByNearness.get(i);
505 if (i < kNearestNeighbors) {
506 float w = weight(xy, p.dimens, pow);
507 sum += w * p.value / weights;
508 }
509 }
510
511 return sum;
512 }
513
Winson Chung69e04ea2013-12-02 14:43:44 -0800514 /** Returns the search bar top offset */
515 int getSearchBarTopOffset() {
516 if (isTablet() && !isVerticalBarLayout()) {
517 return 4 * edgeMarginPx;
518 } else {
519 return 2 * edgeMarginPx;
520 }
521 }
522
Winson Chungb3800242013-10-24 11:01:54 -0700523 /** Returns the search bar bounds in the current orientation */
524 Rect getSearchBarBounds() {
525 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
526 }
527 /** Returns the search bar bounds in the specified orientation */
528 Rect getSearchBarBounds(int orientation) {
529 Rect bounds = new Rect();
530 if (orientation == CellLayout.LANDSCAPE &&
531 transposeLayoutWithOrientation) {
Winson Chung42b3c062013-12-04 12:09:59 -0800532 if (isLayoutRtl) {
533 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
534 availableWidthPx, availableHeightPx - edgeMarginPx);
535 } else {
536 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
537 availableHeightPx - edgeMarginPx);
538 }
Winson Chungb3800242013-10-24 11:01:54 -0700539 } else {
540 if (isTablet()) {
541 // Pad the left and right of the workspace to ensure consistent spacing
542 // between all icons
543 int width = (orientation == CellLayout.LANDSCAPE)
544 ? Math.max(widthPx, heightPx)
545 : Math.min(widthPx, heightPx);
546 // XXX: If the icon size changes across orientations, we will have to take
547 // that into account here too.
548 int gap = (int) ((width - 2 * edgeMarginPx -
549 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
Winson Chung2cb24712013-12-02 15:00:39 -0800550 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
551 availableWidthPx - (edgeMarginPx + gap),
Winson Chungb3800242013-10-24 11:01:54 -0700552 searchBarSpaceHeightPx);
553 } else {
Winson Chung2cb24712013-12-02 15:00:39 -0800554 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
555 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700556 availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
557 defaultWidgetPadding.right), searchBarSpaceHeightPx);
558 }
559 }
560 return bounds;
561 }
562
Winson Chunga6945242014-01-08 14:04:34 -0800563 /** Returns the bounds of the workspace page indicators. */
564 Rect getWorkspacePageIndicatorBounds(Rect insets) {
565 Rect workspacePadding = getWorkspacePadding();
Winson Chung205cd772014-01-15 14:31:59 -0800566 if (isLandscape && transposeLayoutWithOrientation) {
567 if (isLayoutRtl) {
568 return new Rect(workspacePadding.left, workspacePadding.top,
569 workspacePadding.left + pageIndicatorHeightPx,
570 heightPx - workspacePadding.bottom - insets.bottom);
571 } else {
572 int pageIndicatorLeft = widthPx - workspacePadding.right;
573 return new Rect(pageIndicatorLeft, workspacePadding.top,
574 pageIndicatorLeft + pageIndicatorHeightPx,
575 heightPx - workspacePadding.bottom - insets.bottom);
576 }
577 } else {
578 int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom;
579 return new Rect(workspacePadding.left, pageIndicatorTop,
580 widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx);
581 }
Winson Chunga6945242014-01-08 14:04:34 -0800582 }
583
Adam Cohen59400422014-03-05 18:07:04 -0800584 public int getWorkspaceGridHeight() {
585 Rect p = getWorkspacePadding();
586 return availableHeightPx - p.top - p.bottom;
587 }
588
589 public int getWorkspaceGridWidth() {
590 Rect p = getWorkspacePadding();
591 return availableWidthPx - p.left - p.right;
592 }
593
Winson Chungb3800242013-10-24 11:01:54 -0700594 /** Returns the workspace padding in the specified orientation */
595 Rect getWorkspacePadding() {
596 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
597 }
598 Rect getWorkspacePadding(int orientation) {
599 Rect searchBarBounds = getSearchBarBounds(orientation);
600 Rect padding = new Rect();
601 if (orientation == CellLayout.LANDSCAPE &&
602 transposeLayoutWithOrientation) {
603 // Pad the left and right of the workspace with search/hotseat bar sizes
Winson Chung42b3c062013-12-04 12:09:59 -0800604 if (isLayoutRtl) {
605 padding.set(hotseatBarHeightPx, edgeMarginPx,
606 searchBarBounds.width(), edgeMarginPx);
607 } else {
608 padding.set(searchBarBounds.width(), edgeMarginPx,
609 hotseatBarHeightPx, edgeMarginPx);
610 }
Winson Chungb3800242013-10-24 11:01:54 -0700611 } else {
612 if (isTablet()) {
613 // Pad the left and right of the workspace to ensure consistent spacing
614 // between all icons
Winson Chung59a488a2013-12-10 12:32:14 -0800615 float gapScale = 1f + (dragViewScale - 1f) / 2f;
Winson Chungb3800242013-10-24 11:01:54 -0700616 int width = (orientation == CellLayout.LANDSCAPE)
617 ? Math.max(widthPx, heightPx)
618 : Math.min(widthPx, heightPx);
Winson Chung59a488a2013-12-10 12:32:14 -0800619 int height = (orientation != CellLayout.LANDSCAPE)
620 ? Math.max(widthPx, heightPx)
621 : Math.min(widthPx, heightPx);
622 int paddingTop = searchBarBounds.bottom;
623 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
624 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
625 (numColumns * gapScale * cellWidthPx)));
626 int availableHeight = Math.max(0, height - paddingTop - paddingBottom
627 - (int) (2 * numRows * cellHeightPx));
628 padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
629 availableWidth / 2, paddingBottom + availableHeight / 2);
Winson Chungb3800242013-10-24 11:01:54 -0700630 } else {
631 // Pad the top and bottom of the workspace with search/hotseat bar sizes
632 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
633 searchBarBounds.bottom,
634 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
635 hotseatBarHeightPx + pageIndicatorHeightPx);
636 }
637 }
638 return padding;
639 }
640
641 int getWorkspacePageSpacing(int orientation) {
Winson Chung59a488a2013-12-10 12:32:14 -0800642 if ((orientation == CellLayout.LANDSCAPE &&
643 transposeLayoutWithOrientation) || isLargeTablet()) {
Winson Chungb3800242013-10-24 11:01:54 -0700644 // In landscape mode the page spacing is set to the default.
645 return defaultPageSpacingPx;
646 } else {
647 // In portrait, we want the pages spaced such that there is no
648 // overhang of the previous / next page into the current page viewport.
649 // We assume symmetrical padding in portrait mode.
Adam Cohenefb31e32014-01-16 16:07:50 -0800650 return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
Winson Chungb3800242013-10-24 11:01:54 -0700651 }
652 }
653
654 Rect getOverviewModeButtonBarRect() {
655 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
656 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
657 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
658 return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
659 }
660
661 float getOverviewModeScale() {
662 Rect workspacePadding = getWorkspacePadding();
663 Rect overviewBar = getOverviewModeButtonBarRect();
664 int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
665 return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
666 }
667
668 // The rect returned will be extended to below the system ui that covers the workspace
669 Rect getHotseatRect() {
670 if (isVerticalBarLayout()) {
671 return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
672 Integer.MAX_VALUE, availableHeightPx);
673 } else {
674 return new Rect(0, availableHeightPx - hotseatBarHeightPx,
675 availableWidthPx, Integer.MAX_VALUE);
676 }
677 }
678
679 int calculateCellWidth(int width, int countX) {
680 return width / countX;
681 }
682 int calculateCellHeight(int height, int countY) {
683 return height / countY;
684 }
685
686 boolean isPhone() {
687 return !isTablet && !isLargeTablet;
688 }
689 boolean isTablet() {
690 return isTablet;
691 }
692 boolean isLargeTablet() {
693 return isLargeTablet;
694 }
695
696 boolean isVerticalBarLayout() {
697 return isLandscape && transposeLayoutWithOrientation;
698 }
699
700 boolean shouldFadeAdjacentWorkspaceScreens() {
701 return isVerticalBarLayout() || isLargeTablet();
702 }
703
Jorim Jaggid017f882014-01-14 17:08:48 -0800704 int getVisibleChildCount(ViewGroup parent) {
705 int visibleChildren = 0;
706 for (int i = 0; i < parent.getChildCount(); i++) {
707 if (parent.getChildAt(i).getVisibility() != View.GONE) {
708 visibleChildren++;
709 }
710 }
711 return visibleChildren;
712 }
713
Winson Chungb3800242013-10-24 11:01:54 -0700714 public void layout(Launcher launcher) {
715 FrameLayout.LayoutParams lp;
716 Resources res = launcher.getResources();
717 boolean hasVerticalBarLayout = isVerticalBarLayout();
718
719 // Layout the search bar space
720 View searchBar = launcher.getSearchBar();
721 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
722 if (hasVerticalBarLayout) {
Winson Chung69e04ea2013-12-02 14:43:44 -0800723 // Vertical search bar space
Winson Chungb3800242013-10-24 11:01:54 -0700724 lp.gravity = Gravity.TOP | Gravity.LEFT;
725 lp.width = searchBarSpaceHeightPx;
Adam Cohen24ce0b32014-01-14 16:18:14 -0800726 lp.height = LayoutParams.WRAP_CONTENT;
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;
Winson Chungb3800242013-10-24 11:01:54 -0700735 }
736 searchBar.setLayoutParams(lp);
737
Winson Chungb3800242013-10-24 11:01:54 -0700738 // Layout the workspace
739 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
740 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
741 lp.gravity = Gravity.CENTER;
742 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
743 Rect padding = getWorkspacePadding(orientation);
744 workspace.setLayoutParams(lp);
745 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
746 workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
747
748 // Layout the hotseat
749 View hotseat = launcher.findViewById(R.id.hotseat);
750 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
751 if (hasVerticalBarLayout) {
752 // Vertical hotseat
Winson Chung42b3c062013-12-04 12:09:59 -0800753 lp.gravity = Gravity.END;
Winson Chungb3800242013-10-24 11:01:54 -0700754 lp.width = hotseatBarHeightPx;
755 lp.height = LayoutParams.MATCH_PARENT;
756 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
757 } else if (isTablet()) {
Winson Chung59a488a2013-12-10 12:32:14 -0800758 // Pad the hotseat with the workspace padding calculated above
Winson Chungb3800242013-10-24 11:01:54 -0700759 lp.gravity = Gravity.BOTTOM;
760 lp.width = LayoutParams.MATCH_PARENT;
761 lp.height = hotseatBarHeightPx;
Winson Chung59a488a2013-12-10 12:32:14 -0800762 hotseat.setPadding(edgeMarginPx + padding.left, 0,
763 edgeMarginPx + padding.right,
Winson Chungb3800242013-10-24 11:01:54 -0700764 2 * edgeMarginPx);
765 } else {
766 // For phones, layout the hotseat without any bottom margin
767 // to ensure that we have space for the folders
768 lp.gravity = Gravity.BOTTOM;
769 lp.width = LayoutParams.MATCH_PARENT;
770 lp.height = hotseatBarHeightPx;
Sunny Goyald81992b2015-03-05 14:00:18 -0800771 hotseat.setPadding(2 * edgeMarginPx, 0, 2 * edgeMarginPx, 0);
Winson Chungb3800242013-10-24 11:01:54 -0700772 }
773 hotseat.setLayoutParams(lp);
774
775 // Layout the page indicators
776 View pageIndicator = launcher.findViewById(R.id.page_indicator);
777 if (pageIndicator != null) {
778 if (hasVerticalBarLayout) {
779 // Hide the page indicators when we have vertical search/hotseat
780 pageIndicator.setVisibility(View.GONE);
781 } else {
782 // Put the page indicators above the hotseat
783 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
784 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
785 lp.width = LayoutParams.WRAP_CONTENT;
786 lp.height = LayoutParams.WRAP_CONTENT;
787 lp.bottomMargin = hotseatBarHeightPx;
788 pageIndicator.setLayoutParams(lp);
789 }
790 }
791
792 // Layout AllApps
793 AppsCustomizeTabHost host = (AppsCustomizeTabHost)
794 launcher.findViewById(R.id.apps_customize_pane);
795 if (host != null) {
796 // Center the all apps page indicator
797 int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
798 (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
799 pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
800 if (pageIndicator != null) {
Adam Cohen96bb7982014-07-07 11:58:56 -0700801 LinearLayout.LayoutParams lllp = (LinearLayout.LayoutParams) pageIndicator.getLayoutParams();
802 lllp.width = LayoutParams.WRAP_CONTENT;
803 lllp.height = pageIndicatorHeight;
804 pageIndicator.setLayoutParams(lllp);
Winson Chungb3800242013-10-24 11:01:54 -0700805 }
806
807 AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
808 host.findViewById(R.id.apps_customize_pane_content);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700809
810 FrameLayout fakePageContainer = (FrameLayout)
811 host.findViewById(R.id.fake_page_container);
812 FrameLayout fakePage = (FrameLayout) host.findViewById(R.id.fake_page);
813
Winson Chungb3800242013-10-24 11:01:54 -0700814 padding = new Rect();
815 if (pagedView != null) {
816 // Constrain the dimensions of all apps so that it does not span the full width
817 int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
818 (2 * (allAppsNumCols + 1));
819 int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
820 (2 * (allAppsNumRows + 1));
821 paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
822 paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
823 int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
824 int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
Winson Chung495f44d2013-12-04 12:51:53 -0800825 // Only adjust the side paddings on landscape phones, or tablets
826 if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
Winson Chungb3800242013-10-24 11:01:54 -0700827 padding.left = padding.right = gridPaddingLR;
828 }
Adam Cohen9bfdb762014-07-21 17:44:06 -0700829
Winson Chungb3800242013-10-24 11:01:54 -0700830 // The icons are centered, so we can't just offset by the page indicator height
831 // because the empty space will actually be pageIndicatorHeight + paddingTB
832 padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700833
Winson Chungb3800242013-10-24 11:01:54 -0700834 pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700835 fakePage.setBackground(res.getDrawable(R.drawable.quantum_panel));
Adam Cohen96bb7982014-07-07 11:58:56 -0700836
837 // Horizontal padding for the whole paged view
Adam Cohen9bfdb762014-07-21 17:44:06 -0700838 int pagedFixedViewPadding =
Adam Cohen96bb7982014-07-07 11:58:56 -0700839 res.getDimensionPixelSize(R.dimen.apps_customize_horizontal_padding);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700840
841 padding.left += pagedFixedViewPadding;
842 padding.right += pagedFixedViewPadding;
843
844 pagedView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
845 fakePageContainer.setPadding(padding.left, padding.top, padding.right, padding.bottom);
846
Winson Chungb3800242013-10-24 11:01:54 -0700847 }
848 }
849
850 // Layout the Overview Mode
Jorim Jaggid017f882014-01-14 17:08:48 -0800851 ViewGroup overviewMode = launcher.getOverviewPanel();
Winson Chungb3800242013-10-24 11:01:54 -0700852 if (overviewMode != null) {
853 Rect r = getOverviewModeButtonBarRect();
854 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
855 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
Sunny Goyal8dfe2da2014-10-24 16:26:52 -0700856
857 int visibleChildCount = getVisibleChildCount(overviewMode);
858 int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
859 int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
860
861 lp.width = Math.min(availableWidthPx, maxWidth);
Winson Chungb3800242013-10-24 11:01:54 -0700862 lp.height = r.height();
863 overviewMode.setLayoutParams(lp);
Sunny Goyal8dfe2da2014-10-24 16:26:52 -0700864
865 if (lp.width > totalItemWidth && visibleChildCount > 1) {
866 // We have enough space. Lets add some margin too.
867 int margin = (lp.width - totalItemWidth) / (visibleChildCount-1);
868 View lastChild = null;
869
870 // Set margin of all visible children except the last visible child
871 for (int i = 0; i < visibleChildCount; i++) {
872 if (lastChild != null) {
873 MarginLayoutParams clp = (MarginLayoutParams) lastChild.getLayoutParams();
874 if (isLayoutRtl) {
875 clp.leftMargin = margin;
876 } else {
877 clp.rightMargin = margin;
878 }
879 lastChild.setLayoutParams(clp);
880 lastChild = null;
881 }
882 View thisChild = overviewMode.getChildAt(i);
883 if (thisChild.getVisibility() != View.GONE) {
884 lastChild = thisChild;
885 }
886 }
887 }
Winson Chungb3800242013-10-24 11:01:54 -0700888 }
889 }
890}