blob: b97f0f2f787bf3a1f5d2d66f37ccb7160553b636 [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;
125 int searchBarSpaceWidthPx;
Winson Chungb3800242013-10-24 11:01:54 -0700126 int searchBarSpaceHeightPx;
Winson Chungb3800242013-10-24 11:01:54 -0700127 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)
Sunny Goyalc9acdd52015-02-26 12:34:42 -0800140 if (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
Sunny Goyal594d76d2014-11-06 10:12:54 -0800374 searchBarSpaceWidthPx = Math.min(widthPx,
375 resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
376 searchBarSpaceHeightPx = getSearchBarTopOffset()
377 + resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
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
Winson Chungb3800242013-10-24 11:01:54 -0700400 allAppsCellWidthPx = allAppsIconSizePx;
401 allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
402 int maxLongEdgeCellCount =
403 resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
404 int maxShortEdgeCellCount =
405 resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
406 int minEdgeCellCount =
407 resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
408 int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
409 int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
410
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700411 if (allAppsShortEdgeCount > 0 && allAppsLongEdgeCount > 0) {
412 allAppsNumRows = isLandscape ? allAppsShortEdgeCount : allAppsLongEdgeCount;
413 allAppsNumCols = isLandscape ? allAppsLongEdgeCount : allAppsShortEdgeCount;
414 } else {
415 allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
416 (allAppsCellHeightPx + allAppsCellPaddingPx);
417 allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
418 allAppsNumCols = (availableWidthPx) /
419 (allAppsCellWidthPx + allAppsCellPaddingPx);
420 allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
421 }
Winson Chungb3800242013-10-24 11:01:54 -0700422 }
423
424 void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
425 int awPx, int ahPx) {
Winson Chung42b3c062013-12-04 12:09:59 -0800426 Configuration configuration = resources.getConfiguration();
427 isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
Winson Chungb3800242013-10-24 11:01:54 -0700428 isTablet = resources.getBoolean(R.bool.is_tablet);
429 isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
Winson Chung6033ceb2014-02-05 12:37:42 -0800430 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
431 isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
432 } else {
433 isLayoutRtl = false;
434 }
Winson Chungb3800242013-10-24 11:01:54 -0700435 widthPx = wPx;
436 heightPx = hPx;
437 availableWidthPx = awPx;
438 availableHeightPx = ahPx;
439
440 updateAvailableDimensions(context);
441 }
442
443 private float dist(PointF p0, PointF p1) {
444 return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
445 (p1.y-p0.y)*(p1.y-p0.y));
446 }
447
448 private float weight(PointF a, PointF b,
449 float pow) {
450 float d = dist(a, b);
451 if (d == 0f) {
452 return Float.POSITIVE_INFINITY;
453 }
454 return (float) (1f / Math.pow(d, pow));
455 }
456
Winson Chungbe876472014-05-14 14:15:20 -0700457 /** Returns the closest device profile given the width and height and a list of profiles */
458 private DeviceProfile findClosestDeviceProfile(float width, float height,
459 ArrayList<DeviceProfileQuery> points) {
460 return findClosestDeviceProfiles(width, height, points).get(0).profile;
461 }
462
463 /** Returns the closest device profiles ordered by closeness to the specified width and height */
464 private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height,
465 ArrayList<DeviceProfileQuery> points) {
466 final PointF xy = new PointF(width, height);
467
468 // Sort the profiles by their closeness to the dimensions
469 ArrayList<DeviceProfileQuery> pointsByNearness = points;
470 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
471 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
472 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
473 }
474 });
475
476 return pointsByNearness;
477 }
478
Winson Chungb3800242013-10-24 11:01:54 -0700479 private float invDistWeightedInterpolate(float width, float height,
480 ArrayList<DeviceProfileQuery> points) {
481 float sum = 0;
482 float weights = 0;
483 float pow = 5;
484 float kNearestNeighbors = 3;
485 final PointF xy = new PointF(width, height);
486
Winson Chungbe876472014-05-14 14:15:20 -0700487 ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height,
488 points);
Winson Chungb3800242013-10-24 11:01:54 -0700489
490 for (int i = 0; i < pointsByNearness.size(); ++i) {
491 DeviceProfileQuery p = pointsByNearness.get(i);
492 if (i < kNearestNeighbors) {
493 float w = weight(xy, p.dimens, pow);
494 if (w == Float.POSITIVE_INFINITY) {
495 return p.value;
496 }
497 weights += w;
498 }
499 }
500
501 for (int i = 0; i < pointsByNearness.size(); ++i) {
502 DeviceProfileQuery p = pointsByNearness.get(i);
503 if (i < kNearestNeighbors) {
504 float w = weight(xy, p.dimens, pow);
505 sum += w * p.value / weights;
506 }
507 }
508
509 return sum;
510 }
511
Winson Chung69e04ea2013-12-02 14:43:44 -0800512 /** Returns the search bar top offset */
513 int getSearchBarTopOffset() {
514 if (isTablet() && !isVerticalBarLayout()) {
515 return 4 * edgeMarginPx;
516 } else {
517 return 2 * edgeMarginPx;
518 }
519 }
520
Winson Chungb3800242013-10-24 11:01:54 -0700521 /** Returns the search bar bounds in the current orientation */
522 Rect getSearchBarBounds() {
523 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
524 }
525 /** Returns the search bar bounds in the specified orientation */
526 Rect getSearchBarBounds(int orientation) {
527 Rect bounds = new Rect();
528 if (orientation == CellLayout.LANDSCAPE &&
529 transposeLayoutWithOrientation) {
Winson Chung42b3c062013-12-04 12:09:59 -0800530 if (isLayoutRtl) {
531 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
532 availableWidthPx, availableHeightPx - edgeMarginPx);
533 } else {
534 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
535 availableHeightPx - edgeMarginPx);
536 }
Winson Chungb3800242013-10-24 11:01:54 -0700537 } else {
538 if (isTablet()) {
539 // Pad the left and right of the workspace to ensure consistent spacing
540 // between all icons
541 int width = (orientation == CellLayout.LANDSCAPE)
542 ? Math.max(widthPx, heightPx)
543 : Math.min(widthPx, heightPx);
544 // XXX: If the icon size changes across orientations, we will have to take
545 // that into account here too.
546 int gap = (int) ((width - 2 * edgeMarginPx -
547 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
Winson Chung2cb24712013-12-02 15:00:39 -0800548 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
549 availableWidthPx - (edgeMarginPx + gap),
Winson Chungb3800242013-10-24 11:01:54 -0700550 searchBarSpaceHeightPx);
551 } else {
Winson Chung2cb24712013-12-02 15:00:39 -0800552 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
553 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700554 availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
555 defaultWidgetPadding.right), searchBarSpaceHeightPx);
556 }
557 }
558 return bounds;
559 }
560
Winson Chunga6945242014-01-08 14:04:34 -0800561 /** Returns the bounds of the workspace page indicators. */
562 Rect getWorkspacePageIndicatorBounds(Rect insets) {
563 Rect workspacePadding = getWorkspacePadding();
Winson Chung205cd772014-01-15 14:31:59 -0800564 if (isLandscape && transposeLayoutWithOrientation) {
565 if (isLayoutRtl) {
566 return new Rect(workspacePadding.left, workspacePadding.top,
567 workspacePadding.left + pageIndicatorHeightPx,
568 heightPx - workspacePadding.bottom - insets.bottom);
569 } else {
570 int pageIndicatorLeft = widthPx - workspacePadding.right;
571 return new Rect(pageIndicatorLeft, workspacePadding.top,
572 pageIndicatorLeft + pageIndicatorHeightPx,
573 heightPx - workspacePadding.bottom - insets.bottom);
574 }
575 } else {
576 int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom;
577 return new Rect(workspacePadding.left, pageIndicatorTop,
578 widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx);
579 }
Winson Chunga6945242014-01-08 14:04:34 -0800580 }
581
Adam Cohen59400422014-03-05 18:07:04 -0800582 public int getWorkspaceGridHeight() {
583 Rect p = getWorkspacePadding();
584 return availableHeightPx - p.top - p.bottom;
585 }
586
587 public int getWorkspaceGridWidth() {
588 Rect p = getWorkspacePadding();
589 return availableWidthPx - p.left - p.right;
590 }
591
Winson Chungb3800242013-10-24 11:01:54 -0700592 /** Returns the workspace padding in the specified orientation */
593 Rect getWorkspacePadding() {
594 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
595 }
596 Rect getWorkspacePadding(int orientation) {
597 Rect searchBarBounds = getSearchBarBounds(orientation);
598 Rect padding = new Rect();
599 if (orientation == CellLayout.LANDSCAPE &&
600 transposeLayoutWithOrientation) {
601 // Pad the left and right of the workspace with search/hotseat bar sizes
Winson Chung42b3c062013-12-04 12:09:59 -0800602 if (isLayoutRtl) {
603 padding.set(hotseatBarHeightPx, edgeMarginPx,
604 searchBarBounds.width(), edgeMarginPx);
605 } else {
606 padding.set(searchBarBounds.width(), edgeMarginPx,
607 hotseatBarHeightPx, edgeMarginPx);
608 }
Winson Chungb3800242013-10-24 11:01:54 -0700609 } else {
610 if (isTablet()) {
611 // Pad the left and right of the workspace to ensure consistent spacing
612 // between all icons
Winson Chung59a488a2013-12-10 12:32:14 -0800613 float gapScale = 1f + (dragViewScale - 1f) / 2f;
Winson Chungb3800242013-10-24 11:01:54 -0700614 int width = (orientation == CellLayout.LANDSCAPE)
615 ? Math.max(widthPx, heightPx)
616 : Math.min(widthPx, heightPx);
Winson Chung59a488a2013-12-10 12:32:14 -0800617 int height = (orientation != CellLayout.LANDSCAPE)
618 ? Math.max(widthPx, heightPx)
619 : Math.min(widthPx, heightPx);
620 int paddingTop = searchBarBounds.bottom;
621 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
622 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
623 (numColumns * gapScale * cellWidthPx)));
624 int availableHeight = Math.max(0, height - paddingTop - paddingBottom
625 - (int) (2 * numRows * cellHeightPx));
626 padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
627 availableWidth / 2, paddingBottom + availableHeight / 2);
Winson Chungb3800242013-10-24 11:01:54 -0700628 } else {
629 // Pad the top and bottom of the workspace with search/hotseat bar sizes
630 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
631 searchBarBounds.bottom,
632 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
633 hotseatBarHeightPx + pageIndicatorHeightPx);
634 }
635 }
636 return padding;
637 }
638
639 int getWorkspacePageSpacing(int orientation) {
Winson Chung59a488a2013-12-10 12:32:14 -0800640 if ((orientation == CellLayout.LANDSCAPE &&
641 transposeLayoutWithOrientation) || isLargeTablet()) {
Winson Chungb3800242013-10-24 11:01:54 -0700642 // In landscape mode the page spacing is set to the default.
643 return defaultPageSpacingPx;
644 } else {
645 // In portrait, we want the pages spaced such that there is no
646 // overhang of the previous / next page into the current page viewport.
647 // We assume symmetrical padding in portrait mode.
Adam Cohenefb31e32014-01-16 16:07:50 -0800648 return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
Winson Chungb3800242013-10-24 11:01:54 -0700649 }
650 }
651
652 Rect getOverviewModeButtonBarRect() {
653 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
654 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
655 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
656 return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
657 }
658
659 float getOverviewModeScale() {
660 Rect workspacePadding = getWorkspacePadding();
661 Rect overviewBar = getOverviewModeButtonBarRect();
662 int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
663 return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
664 }
665
666 // The rect returned will be extended to below the system ui that covers the workspace
667 Rect getHotseatRect() {
668 if (isVerticalBarLayout()) {
669 return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
670 Integer.MAX_VALUE, availableHeightPx);
671 } else {
672 return new Rect(0, availableHeightPx - hotseatBarHeightPx,
673 availableWidthPx, Integer.MAX_VALUE);
674 }
675 }
676
677 int calculateCellWidth(int width, int countX) {
678 return width / countX;
679 }
680 int calculateCellHeight(int height, int countY) {
681 return height / countY;
682 }
683
684 boolean isPhone() {
685 return !isTablet && !isLargeTablet;
686 }
687 boolean isTablet() {
688 return isTablet;
689 }
690 boolean isLargeTablet() {
691 return isLargeTablet;
692 }
693
694 boolean isVerticalBarLayout() {
695 return isLandscape && transposeLayoutWithOrientation;
696 }
697
698 boolean shouldFadeAdjacentWorkspaceScreens() {
699 return isVerticalBarLayout() || isLargeTablet();
700 }
701
Jorim Jaggid017f882014-01-14 17:08:48 -0800702 int getVisibleChildCount(ViewGroup parent) {
703 int visibleChildren = 0;
704 for (int i = 0; i < parent.getChildCount(); i++) {
705 if (parent.getChildAt(i).getVisibility() != View.GONE) {
706 visibleChildren++;
707 }
708 }
709 return visibleChildren;
710 }
711
Winson Chungb3800242013-10-24 11:01:54 -0700712 public void layout(Launcher launcher) {
713 FrameLayout.LayoutParams lp;
714 Resources res = launcher.getResources();
715 boolean hasVerticalBarLayout = isVerticalBarLayout();
716
717 // Layout the search bar space
718 View searchBar = launcher.getSearchBar();
719 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
720 if (hasVerticalBarLayout) {
Winson Chung69e04ea2013-12-02 14:43:44 -0800721 // Vertical search bar space
Winson Chungb3800242013-10-24 11:01:54 -0700722 lp.gravity = Gravity.TOP | Gravity.LEFT;
723 lp.width = searchBarSpaceHeightPx;
Adam Cohen24ce0b32014-01-14 16:18:14 -0800724 lp.height = LayoutParams.WRAP_CONTENT;
Adam Cohen24ce0b32014-01-14 16:18:14 -0800725
726 LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
727 targets.setOrientation(LinearLayout.VERTICAL);
Winson Chungb3800242013-10-24 11:01:54 -0700728 } else {
Winson Chung69e04ea2013-12-02 14:43:44 -0800729 // Horizontal search bar space
Winson Chungb3800242013-10-24 11:01:54 -0700730 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
731 lp.width = searchBarSpaceWidthPx;
732 lp.height = searchBarSpaceHeightPx;
Winson Chungb3800242013-10-24 11:01:54 -0700733 }
734 searchBar.setLayoutParams(lp);
735
Winson Chungb3800242013-10-24 11:01:54 -0700736 // Layout the workspace
737 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
738 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
739 lp.gravity = Gravity.CENTER;
740 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
741 Rect padding = getWorkspacePadding(orientation);
742 workspace.setLayoutParams(lp);
743 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
744 workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
745
746 // Layout the hotseat
747 View hotseat = launcher.findViewById(R.id.hotseat);
748 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
749 if (hasVerticalBarLayout) {
750 // Vertical hotseat
Winson Chung42b3c062013-12-04 12:09:59 -0800751 lp.gravity = Gravity.END;
Winson Chungb3800242013-10-24 11:01:54 -0700752 lp.width = hotseatBarHeightPx;
753 lp.height = LayoutParams.MATCH_PARENT;
754 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
755 } else if (isTablet()) {
Winson Chung59a488a2013-12-10 12:32:14 -0800756 // Pad the hotseat with the workspace padding calculated above
Winson Chungb3800242013-10-24 11:01:54 -0700757 lp.gravity = Gravity.BOTTOM;
758 lp.width = LayoutParams.MATCH_PARENT;
759 lp.height = hotseatBarHeightPx;
Winson Chung59a488a2013-12-10 12:32:14 -0800760 hotseat.setPadding(edgeMarginPx + padding.left, 0,
761 edgeMarginPx + padding.right,
Winson Chungb3800242013-10-24 11:01:54 -0700762 2 * edgeMarginPx);
763 } else {
764 // For phones, layout the hotseat without any bottom margin
765 // to ensure that we have space for the folders
766 lp.gravity = Gravity.BOTTOM;
767 lp.width = LayoutParams.MATCH_PARENT;
768 lp.height = hotseatBarHeightPx;
769 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
770 2 * edgeMarginPx, 0);
771 }
772 hotseat.setLayoutParams(lp);
773
774 // Layout the page indicators
775 View pageIndicator = launcher.findViewById(R.id.page_indicator);
776 if (pageIndicator != null) {
777 if (hasVerticalBarLayout) {
778 // Hide the page indicators when we have vertical search/hotseat
779 pageIndicator.setVisibility(View.GONE);
780 } else {
781 // Put the page indicators above the hotseat
782 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
783 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
784 lp.width = LayoutParams.WRAP_CONTENT;
785 lp.height = LayoutParams.WRAP_CONTENT;
786 lp.bottomMargin = hotseatBarHeightPx;
787 pageIndicator.setLayoutParams(lp);
788 }
789 }
790
791 // Layout AllApps
792 AppsCustomizeTabHost host = (AppsCustomizeTabHost)
793 launcher.findViewById(R.id.apps_customize_pane);
794 if (host != null) {
795 // Center the all apps page indicator
796 int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
797 (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
798 pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
799 if (pageIndicator != null) {
Adam Cohen96bb7982014-07-07 11:58:56 -0700800 LinearLayout.LayoutParams lllp = (LinearLayout.LayoutParams) pageIndicator.getLayoutParams();
801 lllp.width = LayoutParams.WRAP_CONTENT;
802 lllp.height = pageIndicatorHeight;
803 pageIndicator.setLayoutParams(lllp);
Winson Chungb3800242013-10-24 11:01:54 -0700804 }
805
806 AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
807 host.findViewById(R.id.apps_customize_pane_content);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700808
809 FrameLayout fakePageContainer = (FrameLayout)
810 host.findViewById(R.id.fake_page_container);
811 FrameLayout fakePage = (FrameLayout) host.findViewById(R.id.fake_page);
812
Winson Chungb3800242013-10-24 11:01:54 -0700813 padding = new Rect();
814 if (pagedView != null) {
815 // Constrain the dimensions of all apps so that it does not span the full width
816 int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
817 (2 * (allAppsNumCols + 1));
818 int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
819 (2 * (allAppsNumRows + 1));
820 paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
821 paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
822 int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
823 int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
Winson Chung495f44d2013-12-04 12:51:53 -0800824 // Only adjust the side paddings on landscape phones, or tablets
825 if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
Winson Chungb3800242013-10-24 11:01:54 -0700826 padding.left = padding.right = gridPaddingLR;
827 }
Adam Cohen9bfdb762014-07-21 17:44:06 -0700828
Winson Chungb3800242013-10-24 11:01:54 -0700829 // The icons are centered, so we can't just offset by the page indicator height
830 // because the empty space will actually be pageIndicatorHeight + paddingTB
831 padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700832
Winson Chungb3800242013-10-24 11:01:54 -0700833 pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700834 fakePage.setBackground(res.getDrawable(R.drawable.quantum_panel));
Adam Cohen96bb7982014-07-07 11:58:56 -0700835
836 // Horizontal padding for the whole paged view
Adam Cohen9bfdb762014-07-21 17:44:06 -0700837 int pagedFixedViewPadding =
Adam Cohen96bb7982014-07-07 11:58:56 -0700838 res.getDimensionPixelSize(R.dimen.apps_customize_horizontal_padding);
Adam Cohen9bfdb762014-07-21 17:44:06 -0700839
840 padding.left += pagedFixedViewPadding;
841 padding.right += pagedFixedViewPadding;
842
843 pagedView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
844 fakePageContainer.setPadding(padding.left, padding.top, padding.right, padding.bottom);
845
Winson Chungb3800242013-10-24 11:01:54 -0700846 }
847 }
848
849 // Layout the Overview Mode
Jorim Jaggid017f882014-01-14 17:08:48 -0800850 ViewGroup overviewMode = launcher.getOverviewPanel();
Winson Chungb3800242013-10-24 11:01:54 -0700851 if (overviewMode != null) {
852 Rect r = getOverviewModeButtonBarRect();
853 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
854 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
Sunny Goyal8dfe2da2014-10-24 16:26:52 -0700855
856 int visibleChildCount = getVisibleChildCount(overviewMode);
857 int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
858 int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
859
860 lp.width = Math.min(availableWidthPx, maxWidth);
Winson Chungb3800242013-10-24 11:01:54 -0700861 lp.height = r.height();
862 overviewMode.setLayoutParams(lp);
Sunny Goyal8dfe2da2014-10-24 16:26:52 -0700863
864 if (lp.width > totalItemWidth && visibleChildCount > 1) {
865 // We have enough space. Lets add some margin too.
866 int margin = (lp.width - totalItemWidth) / (visibleChildCount-1);
867 View lastChild = null;
868
869 // Set margin of all visible children except the last visible child
870 for (int i = 0; i < visibleChildCount; i++) {
871 if (lastChild != null) {
872 MarginLayoutParams clp = (MarginLayoutParams) lastChild.getLayoutParams();
873 if (isLayoutRtl) {
874 clp.leftMargin = margin;
875 } else {
876 clp.rightMargin = margin;
877 }
878 lastChild.setLayoutParams(clp);
879 lastChild = null;
880 }
881 View thisChild = overviewMode.getChildAt(i);
882 if (thisChild.getVisibility() != View.GONE) {
883 lastChild = thisChild;
884 }
885 }
886 }
Winson Chungb3800242013-10-24 11:01:54 -0700887 }
888 }
889}