blob: 3bbf0e7d8e5aa1ba3460299a3f1c1de5925d1e87 [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
Adam Cohen091440a2015-03-18 14:16:05 -070041import com.android.launcher3.util.Thunk;
42
Winson Chungb3800242013-10-24 11:01:54 -070043import java.util.ArrayList;
44import java.util.Collections;
45import java.util.Comparator;
46
47
48class DeviceProfileQuery {
Winson Chungbe876472014-05-14 14:15:20 -070049 DeviceProfile profile;
Winson Chungb3800242013-10-24 11:01:54 -070050 float widthDps;
51 float heightDps;
52 float value;
53 PointF dimens;
54
Winson Chungbe876472014-05-14 14:15:20 -070055 DeviceProfileQuery(DeviceProfile p, float v) {
56 widthDps = p.minWidthDps;
57 heightDps = p.minHeightDps;
Winson Chungb3800242013-10-24 11:01:54 -070058 value = v;
Winson Chungbe876472014-05-14 14:15:20 -070059 dimens = new PointF(widthDps, heightDps);
60 profile = p;
Winson Chungb3800242013-10-24 11:01:54 -070061 }
62}
63
64public class DeviceProfile {
65 public static interface DeviceProfileCallbacks {
66 public void onAvailableSizeChanged(DeviceProfile grid);
67 }
68
69 String name;
70 float minWidthDps;
71 float minHeightDps;
Sunny Goyalfee35bb2015-05-11 11:38:19 -070072 public int numRows;
73 public int numColumns;
74 public int numFolderRows;
75 public int numFolderColumns;
Winson Chungb3800242013-10-24 11:01:54 -070076 float numHotseatIcons;
Adam Cohen4ae96ce2014-08-29 15:05:48 -070077 float iconSize;
Winson Chungb3800242013-10-24 11:01:54 -070078 private float iconTextSize;
79 private int iconDrawablePaddingOriginalPx;
80 private float hotseatIconSize;
81
Winson Chungbe876472014-05-14 14:15:20 -070082 int defaultLayoutId;
Winson Chungbe876472014-05-14 14:15:20 -070083
Winson Chungb3800242013-10-24 11:01:54 -070084 boolean isLandscape;
Hyunyoung Song4b69f2c2015-05-11 14:55:07 -070085 public boolean isTablet;
86 public boolean isLargeTablet;
Hyunyoung Songada50982015-04-10 14:35:23 -070087 public boolean isLayoutRtl;
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -070088
Winson Chungb3800242013-10-24 11:01:54 -070089 boolean transposeLayoutWithOrientation;
Winson Chungb3800242013-10-24 11:01:54 -070090 int desiredWorkspaceLeftRightMarginPx;
Adam Cohen59400422014-03-05 18:07:04 -080091 public int edgeMarginPx;
Winson Chungb3800242013-10-24 11:01:54 -070092 Rect defaultWidgetPadding;
93
94 int widthPx;
95 int heightPx;
Adam Cohen59400422014-03-05 18:07:04 -080096 public int availableWidthPx;
97 public int availableHeightPx;
Winson Chungb3800242013-10-24 11:01:54 -070098 int defaultPageSpacingPx;
99
100 int overviewModeMinIconZoneHeightPx;
101 int overviewModeMaxIconZoneHeightPx;
Jorim Jaggid017f882014-01-14 17:08:48 -0800102 int overviewModeBarItemWidthPx;
103 int overviewModeBarSpacerWidthPx;
Winson Chungb3800242013-10-24 11:01:54 -0700104 float overviewModeIconZoneRatio;
105 float overviewModeScaleFactor;
106
Adam Cohen59400422014-03-05 18:07:04 -0800107 public int cellWidthPx;
108 public int cellHeightPx;
109
Hyunyoung Song3f471442015-04-08 19:01:34 -0700110 public int iconSizePx;
111 public int iconTextSizePx;
Winson Chungb3800242013-10-24 11:01:54 -0700112 int iconDrawablePaddingPx;
Winson Chungb3800242013-10-24 11:01:54 -0700113 int allAppsIconSizePx;
114 int allAppsIconTextSizePx;
115 int allAppsCellWidthPx;
116 int allAppsCellHeightPx;
117 int allAppsCellPaddingPx;
118 int folderBackgroundOffset;
119 int folderIconSizePx;
120 int folderCellWidthPx;
121 int folderCellHeightPx;
122 int hotseatCellWidthPx;
123 int hotseatCellHeightPx;
124 int hotseatIconSizePx;
125 int hotseatBarHeightPx;
126 int hotseatAllAppsRank;
127 int allAppsNumRows;
128 int allAppsNumCols;
Winson Chung93f98ea2015-03-10 16:28:47 -0700129 int appsViewNumCols;
Winson Chungb3800242013-10-24 11:01:54 -0700130 int searchBarSpaceWidthPx;
Winson Chungb3800242013-10-24 11:01:54 -0700131 int searchBarSpaceHeightPx;
Winson Chungb3800242013-10-24 11:01:54 -0700132 int pageIndicatorHeightPx;
Adam Cohen63f1ec02014-08-12 09:23:13 -0700133 int allAppsButtonVisualSize;
Winson Chungb3800242013-10-24 11:01:54 -0700134
Winson Chung59a488a2013-12-10 12:32:14 -0800135 float dragViewScale;
136
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700137 int allAppsShortEdgeCount = -1;
138 int allAppsLongEdgeCount = -1;
139
Winson Chungb3800242013-10-24 11:01:54 -0700140 private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
141
Sunny Goyalfee35bb2015-05-11 11:38:19 -0700142 DeviceProfile(String n, float w, float h,
143 int r, int c, int fr, int fc,
144 float is, float its, float hs, float his, int dlId) {
Winson Chungb3800242013-10-24 11:01:54 -0700145 // Ensure that we have an odd number of hotseat items (since we need to place all apps)
Sunny Goyalc9acdd52015-02-26 12:34:42 -0800146 if (hs % 2 == 0) {
Winson Chungb3800242013-10-24 11:01:54 -0700147 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
148 }
149
150 name = n;
151 minWidthDps = w;
152 minHeightDps = h;
Sunny Goyalfee35bb2015-05-11 11:38:19 -0700153
Winson Chungb3800242013-10-24 11:01:54 -0700154 numRows = r;
155 numColumns = c;
Sunny Goyalfee35bb2015-05-11 11:38:19 -0700156 numFolderRows = fr;
157 numFolderColumns = fc;
158
Winson Chungb3800242013-10-24 11:01:54 -0700159 iconSize = is;
160 iconTextSize = its;
161 numHotseatIcons = hs;
162 hotseatIconSize = his;
Winson Chungbe876472014-05-14 14:15:20 -0700163 defaultLayoutId = dlId;
Winson Chungb3800242013-10-24 11:01:54 -0700164 }
165
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700166 DeviceProfile() {
167 }
168
Winson Chungb3800242013-10-24 11:01:54 -0700169 DeviceProfile(Context context,
170 ArrayList<DeviceProfile> profiles,
171 float minWidth, float minHeight,
172 int wPx, int hPx,
173 int awPx, int ahPx,
174 Resources res) {
175 DisplayMetrics dm = res.getDisplayMetrics();
176 ArrayList<DeviceProfileQuery> points =
177 new ArrayList<DeviceProfileQuery>();
178 transposeLayoutWithOrientation =
179 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
180 minWidthDps = minWidth;
181 minHeightDps = minHeight;
182
183 ComponentName cn = new ComponentName(context.getPackageName(),
184 this.getClass().getName());
185 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
186 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
187 desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
188 pageIndicatorHeightPx =
189 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
190 defaultPageSpacingPx =
191 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
192 allAppsCellPaddingPx =
193 res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
194 overviewModeMinIconZoneHeightPx =
195 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
196 overviewModeMaxIconZoneHeightPx =
197 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
Jorim Jaggid017f882014-01-14 17:08:48 -0800198 overviewModeBarItemWidthPx =
199 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
200 overviewModeBarSpacerWidthPx =
201 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
Winson Chungb3800242013-10-24 11:01:54 -0700202 overviewModeIconZoneRatio =
203 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
204 overviewModeScaleFactor =
205 res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
206
Winson Chungbe876472014-05-14 14:15:20 -0700207 // Find the closes profile given the width/height
Winson Chungb3800242013-10-24 11:01:54 -0700208 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700209 points.add(new DeviceProfileQuery(p, 0f));
Winson Chungb3800242013-10-24 11:01:54 -0700210 }
Winson Chungbe876472014-05-14 14:15:20 -0700211 DeviceProfile closestProfile = findClosestDeviceProfile(minWidth, minHeight, points);
212
213 // Snap to the closest row count
214 numRows = closestProfile.numRows;
215
216 // Snap to the closest column count
217 numColumns = closestProfile.numColumns;
218
Sunny Goyalfee35bb2015-05-11 11:38:19 -0700219 numFolderRows = closestProfile.numFolderRows;
220 numFolderColumns = closestProfile.numFolderColumns;
221
Winson Chungbe876472014-05-14 14:15:20 -0700222 // Snap to the closest hotseat size
223 numHotseatIcons = closestProfile.numHotseatIcons;
Winson Chungb3800242013-10-24 11:01:54 -0700224 hotseatAllAppsRank = (int) (numHotseatIcons / 2);
225
Winson Chungbe876472014-05-14 14:15:20 -0700226 // Snap to the closest default layout id
227 defaultLayoutId = closestProfile.defaultLayoutId;
228
Winson Chungb3800242013-10-24 11:01:54 -0700229 // Interpolate the icon size
230 points.clear();
231 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700232 points.add(new DeviceProfileQuery(p, p.iconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700233 }
234 iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700235
Winson Chungb3800242013-10-24 11:01:54 -0700236 // AllApps uses the original non-scaled icon size
237 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
238
239 // Interpolate the icon text size
240 points.clear();
241 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700242 points.add(new DeviceProfileQuery(p, p.iconTextSize));
Winson Chungb3800242013-10-24 11:01:54 -0700243 }
244 iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
245 iconDrawablePaddingOriginalPx =
246 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
247 // AllApps uses the original non-scaled icon text size
248 allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
249
250 // Interpolate the hotseat icon size
251 points.clear();
252 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700253 points.add(new DeviceProfileQuery(p, p.hotseatIconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700254 }
255 // Hotseat
256 hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
257
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700258 // If the partner customization apk contains any grid overrides, apply them
259 applyPartnerDeviceProfileOverrides(context, dm);
260
Winson Chungb3800242013-10-24 11:01:54 -0700261 // Calculate the remaining vars
262 updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
263 updateAvailableDimensions(context);
Adam Cohen63f1ec02014-08-12 09:23:13 -0700264 computeAllAppsButtonSize(context);
265 }
266
267 /**
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700268 * Apply any Partner customization grid overrides.
269 *
270 * Currently we support: all apps row / column count.
271 */
272 private void applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm) {
273 Partner p = Partner.get(ctx.getPackageManager());
274 if (p != null) {
275 DeviceProfile partnerDp = p.getDeviceProfileOverride(dm);
276 if (partnerDp != null) {
277 if (partnerDp.numRows > 0 && partnerDp.numColumns > 0) {
Sunny Goyalfee35bb2015-05-11 11:38:19 -0700278 numRows = numFolderRows = partnerDp.numRows;
279 numColumns = numFolderColumns = partnerDp.numColumns;
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700280 }
281 if (partnerDp.allAppsShortEdgeCount > 0 && partnerDp.allAppsLongEdgeCount > 0) {
282 allAppsShortEdgeCount = partnerDp.allAppsShortEdgeCount;
283 allAppsLongEdgeCount = partnerDp.allAppsLongEdgeCount;
284 }
285 if (partnerDp.iconSize > 0) {
286 iconSize = partnerDp.iconSize;
287 // AllApps uses the original non-scaled icon size
288 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
289 }
290 }
291 }
292 }
293
294 /**
Adam Cohen63f1ec02014-08-12 09:23:13 -0700295 * Determine the exact visual footprint of the all apps button, taking into account scaling
296 * and internal padding of the drawable.
297 */
298 private void computeAllAppsButtonSize(Context context) {
299 Resources res = context.getResources();
300 float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
301 LauncherAppState app = LauncherAppState.getInstance();
302 allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding));
Winson Chungb3800242013-10-24 11:01:54 -0700303 }
304
305 void addCallback(DeviceProfileCallbacks cb) {
306 mCallbacks.add(cb);
307 cb.onAvailableSizeChanged(this);
308 }
309 void removeCallback(DeviceProfileCallbacks cb) {
310 mCallbacks.remove(cb);
311 }
312
313 private int getDeviceOrientation(Context context) {
314 WindowManager windowManager = (WindowManager)
315 context.getSystemService(Context.WINDOW_SERVICE);
316 Resources resources = context.getResources();
317 DisplayMetrics dm = resources.getDisplayMetrics();
318 Configuration config = resources.getConfiguration();
319 int rotation = windowManager.getDefaultDisplay().getRotation();
320
321 boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
322 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
323 boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
324 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
325 if (isLandscape || isRotatedPortrait) {
326 return CellLayout.LANDSCAPE;
327 } else {
328 return CellLayout.PORTRAIT;
329 }
330 }
331
332 private void updateAvailableDimensions(Context context) {
333 WindowManager windowManager = (WindowManager)
334 context.getSystemService(Context.WINDOW_SERVICE);
335 Display display = windowManager.getDefaultDisplay();
336 Resources resources = context.getResources();
337 DisplayMetrics dm = resources.getDisplayMetrics();
338 Configuration config = resources.getConfiguration();
339
340 // There are three possible configurations that the dynamic grid accounts for, portrait,
341 // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
342 // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
343 // the height is the smallest height (either with the nav bar at the bottom or to the
344 // side) and otherwise, the height is simply the largest possible height for a portrait
345 // device.
346 Point size = new Point();
347 Point smallestSize = new Point();
348 Point largestSize = new Point();
349 display.getSize(size);
350 display.getCurrentSizeRange(smallestSize, largestSize);
351 availableWidthPx = size.x;
352 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
353 availableHeightPx = smallestSize.y;
354 } else {
355 availableHeightPx = largestSize.y;
356 }
357
358 // Check to see if the icons fit in the new available height. If not, then we need to
359 // shrink the icon size.
Winson Chungb3800242013-10-24 11:01:54 -0700360 float scale = 1f;
361 int drawablePadding = iconDrawablePaddingOriginalPx;
362 updateIconSize(1f, drawablePadding, resources, dm);
363 float usedHeight = (cellHeightPx * numRows);
Winson Chung59a488a2013-12-10 12:32:14 -0800364
365 Rect workspacePadding = getWorkspacePadding();
Winson Chungb3800242013-10-24 11:01:54 -0700366 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
367 if (usedHeight > maxHeight) {
368 scale = maxHeight / usedHeight;
369 drawablePadding = 0;
370 }
371 updateIconSize(scale, drawablePadding, resources, dm);
372
373 // Make the callbacks
374 for (DeviceProfileCallbacks cb : mCallbacks) {
375 cb.onAvailableSizeChanged(this);
376 }
377 }
378
Winson Chung93f98ea2015-03-10 16:28:47 -0700379 private void updateIconSize(float scale, int drawablePadding, Resources res,
Winson Chungb3800242013-10-24 11:01:54 -0700380 DisplayMetrics dm) {
381 iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
382 iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
383 iconDrawablePaddingPx = drawablePadding;
384 hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
385
386 // Search Bar
Sunny Goyal594d76d2014-11-06 10:12:54 -0800387 searchBarSpaceWidthPx = Math.min(widthPx,
Winson Chung93f98ea2015-03-10 16:28:47 -0700388 res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
Sunny Goyal594d76d2014-11-06 10:12:54 -0800389 searchBarSpaceHeightPx = getSearchBarTopOffset()
Winson Chung93f98ea2015-03-10 16:28:47 -0700390 + res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
Winson Chungb3800242013-10-24 11:01:54 -0700391
392 // Calculate the actual text height
393 Paint textPaint = new Paint();
394 textPaint.setTextSize(iconTextSizePx);
395 FontMetrics fm = textPaint.getFontMetrics();
396 cellWidthPx = iconSizePx;
397 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
Winson Chung93f98ea2015-03-10 16:28:47 -0700398 final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
Winson Chung59a488a2013-12-10 12:32:14 -0800399 dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
Winson Chungb3800242013-10-24 11:01:54 -0700400
401 // Hotseat
402 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
403 hotseatCellWidthPx = iconSizePx;
404 hotseatCellHeightPx = iconSizePx;
405
406 // Folder
407 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
408 folderCellHeightPx = cellHeightPx + edgeMarginPx;
409 folderBackgroundOffset = -edgeMarginPx;
410 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
411
412 // All Apps
Winson Chungb3800242013-10-24 11:01:54 -0700413 allAppsCellWidthPx = allAppsIconSizePx;
414 allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
415 int maxLongEdgeCellCount =
Winson Chung93f98ea2015-03-10 16:28:47 -0700416 res.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
Winson Chungb3800242013-10-24 11:01:54 -0700417 int maxShortEdgeCellCount =
Winson Chung93f98ea2015-03-10 16:28:47 -0700418 res.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
Winson Chungb3800242013-10-24 11:01:54 -0700419 int minEdgeCellCount =
Winson Chung93f98ea2015-03-10 16:28:47 -0700420 res.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
Winson Chungb3800242013-10-24 11:01:54 -0700421 int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
422 int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
423
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700424 if (allAppsShortEdgeCount > 0 && allAppsLongEdgeCount > 0) {
425 allAppsNumRows = isLandscape ? allAppsShortEdgeCount : allAppsLongEdgeCount;
426 allAppsNumCols = isLandscape ? allAppsLongEdgeCount : allAppsShortEdgeCount;
427 } else {
428 allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
429 (allAppsCellHeightPx + allAppsCellPaddingPx);
430 allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
Winson Chung93f98ea2015-03-10 16:28:47 -0700431 allAppsNumCols = (availableWidthPx) / (allAppsCellWidthPx + allAppsCellPaddingPx);
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700432 allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
433 }
Winson Chung93f98ea2015-03-10 16:28:47 -0700434
Winson Chung0f785722015-04-08 10:27:49 -0700435 int appsContainerViewWidthPx = res.getDimensionPixelSize(R.dimen.apps_container_width);
436 updateAppsViewNumCols(res, appsContainerViewWidthPx);
437 }
438
439 public boolean updateAppsViewNumCols(Resources res, int containerWidth) {
Winson Chung93f98ea2015-03-10 16:28:47 -0700440 int appsViewLeftMarginPx =
441 res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
Winson Chung0f785722015-04-08 10:27:49 -0700442 int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx;
443 int numCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
Winson Chungbd808532015-04-08 11:07:53 -0700444 (allAppsCellWidthPx + 2 * allAppsCellPaddingPx);
Winson Chung0f785722015-04-08 10:27:49 -0700445 if (numCols != appsViewNumCols) {
446 appsViewNumCols = numCols;
447 return true;
448 }
449 return false;
Winson Chungb3800242013-10-24 11:01:54 -0700450 }
451
452 void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
453 int awPx, int ahPx) {
Winson Chung42b3c062013-12-04 12:09:59 -0800454 Configuration configuration = resources.getConfiguration();
455 isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
Winson Chungb3800242013-10-24 11:01:54 -0700456 isTablet = resources.getBoolean(R.bool.is_tablet);
457 isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
Winson Chung6033ceb2014-02-05 12:37:42 -0800458 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
459 isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
460 } else {
461 isLayoutRtl = false;
462 }
Winson Chungb3800242013-10-24 11:01:54 -0700463 widthPx = wPx;
464 heightPx = hPx;
465 availableWidthPx = awPx;
466 availableHeightPx = ahPx;
467
468 updateAvailableDimensions(context);
469 }
470
Adam Cohen091440a2015-03-18 14:16:05 -0700471 @Thunk float dist(PointF p0, PointF p1) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700472 return (float) Math.hypot(p1.x - p0.x, p1.y - p0.y);
Winson Chungb3800242013-10-24 11:01:54 -0700473 }
474
475 private float weight(PointF a, PointF b,
476 float pow) {
477 float d = dist(a, b);
478 if (d == 0f) {
479 return Float.POSITIVE_INFINITY;
480 }
481 return (float) (1f / Math.pow(d, pow));
482 }
483
Winson Chungbe876472014-05-14 14:15:20 -0700484 /** Returns the closest device profile given the width and height and a list of profiles */
485 private DeviceProfile findClosestDeviceProfile(float width, float height,
486 ArrayList<DeviceProfileQuery> points) {
487 return findClosestDeviceProfiles(width, height, points).get(0).profile;
488 }
489
490 /** Returns the closest device profiles ordered by closeness to the specified width and height */
491 private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height,
492 ArrayList<DeviceProfileQuery> points) {
493 final PointF xy = new PointF(width, height);
494
495 // Sort the profiles by their closeness to the dimensions
496 ArrayList<DeviceProfileQuery> pointsByNearness = points;
497 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
498 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
499 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
500 }
501 });
502
503 return pointsByNearness;
504 }
505
Winson Chungb3800242013-10-24 11:01:54 -0700506 private float invDistWeightedInterpolate(float width, float height,
507 ArrayList<DeviceProfileQuery> points) {
508 float sum = 0;
509 float weights = 0;
510 float pow = 5;
511 float kNearestNeighbors = 3;
512 final PointF xy = new PointF(width, height);
513
Winson Chungbe876472014-05-14 14:15:20 -0700514 ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height,
515 points);
Winson Chungb3800242013-10-24 11:01:54 -0700516
517 for (int i = 0; i < pointsByNearness.size(); ++i) {
518 DeviceProfileQuery p = pointsByNearness.get(i);
519 if (i < kNearestNeighbors) {
520 float w = weight(xy, p.dimens, pow);
521 if (w == Float.POSITIVE_INFINITY) {
522 return p.value;
523 }
524 weights += w;
525 }
526 }
527
528 for (int i = 0; i < pointsByNearness.size(); ++i) {
529 DeviceProfileQuery p = pointsByNearness.get(i);
530 if (i < kNearestNeighbors) {
531 float w = weight(xy, p.dimens, pow);
532 sum += w * p.value / weights;
533 }
534 }
535
536 return sum;
537 }
538
Winson Chung69e04ea2013-12-02 14:43:44 -0800539 /** Returns the search bar top offset */
540 int getSearchBarTopOffset() {
541 if (isTablet() && !isVerticalBarLayout()) {
542 return 4 * edgeMarginPx;
543 } else {
544 return 2 * edgeMarginPx;
545 }
546 }
547
Winson Chungb3800242013-10-24 11:01:54 -0700548 /** Returns the search bar bounds in the current orientation */
549 Rect getSearchBarBounds() {
550 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
551 }
552 /** Returns the search bar bounds in the specified orientation */
553 Rect getSearchBarBounds(int orientation) {
554 Rect bounds = new Rect();
555 if (orientation == CellLayout.LANDSCAPE &&
556 transposeLayoutWithOrientation) {
Winson Chung42b3c062013-12-04 12:09:59 -0800557 if (isLayoutRtl) {
558 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
559 availableWidthPx, availableHeightPx - edgeMarginPx);
560 } else {
561 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
562 availableHeightPx - edgeMarginPx);
563 }
Winson Chungb3800242013-10-24 11:01:54 -0700564 } else {
565 if (isTablet()) {
566 // Pad the left and right of the workspace to ensure consistent spacing
567 // between all icons
568 int width = (orientation == CellLayout.LANDSCAPE)
569 ? Math.max(widthPx, heightPx)
570 : Math.min(widthPx, heightPx);
571 // XXX: If the icon size changes across orientations, we will have to take
572 // that into account here too.
573 int gap = (int) ((width - 2 * edgeMarginPx -
574 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
Winson Chung2cb24712013-12-02 15:00:39 -0800575 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
576 availableWidthPx - (edgeMarginPx + gap),
Winson Chungb3800242013-10-24 11:01:54 -0700577 searchBarSpaceHeightPx);
578 } else {
Winson Chung2cb24712013-12-02 15:00:39 -0800579 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
580 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700581 availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
582 defaultWidgetPadding.right), searchBarSpaceHeightPx);
583 }
584 }
585 return bounds;
586 }
587
Winson Chunga6945242014-01-08 14:04:34 -0800588 /** Returns the bounds of the workspace page indicators. */
589 Rect getWorkspacePageIndicatorBounds(Rect insets) {
590 Rect workspacePadding = getWorkspacePadding();
Winson Chung205cd772014-01-15 14:31:59 -0800591 if (isLandscape && transposeLayoutWithOrientation) {
592 if (isLayoutRtl) {
593 return new Rect(workspacePadding.left, workspacePadding.top,
594 workspacePadding.left + pageIndicatorHeightPx,
595 heightPx - workspacePadding.bottom - insets.bottom);
596 } else {
597 int pageIndicatorLeft = widthPx - workspacePadding.right;
598 return new Rect(pageIndicatorLeft, workspacePadding.top,
599 pageIndicatorLeft + pageIndicatorHeightPx,
600 heightPx - workspacePadding.bottom - insets.bottom);
601 }
602 } else {
603 int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom;
604 return new Rect(workspacePadding.left, pageIndicatorTop,
605 widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx);
606 }
Winson Chunga6945242014-01-08 14:04:34 -0800607 }
608
Adam Cohen59400422014-03-05 18:07:04 -0800609 public int getWorkspaceGridHeight() {
610 Rect p = getWorkspacePadding();
611 return availableHeightPx - p.top - p.bottom;
612 }
613
614 public int getWorkspaceGridWidth() {
615 Rect p = getWorkspacePadding();
616 return availableWidthPx - p.left - p.right;
617 }
618
Winson Chungb3800242013-10-24 11:01:54 -0700619 /** Returns the workspace padding in the specified orientation */
620 Rect getWorkspacePadding() {
621 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
622 }
623 Rect getWorkspacePadding(int orientation) {
624 Rect searchBarBounds = getSearchBarBounds(orientation);
625 Rect padding = new Rect();
626 if (orientation == CellLayout.LANDSCAPE &&
627 transposeLayoutWithOrientation) {
628 // Pad the left and right of the workspace with search/hotseat bar sizes
Winson Chung42b3c062013-12-04 12:09:59 -0800629 if (isLayoutRtl) {
630 padding.set(hotseatBarHeightPx, edgeMarginPx,
631 searchBarBounds.width(), edgeMarginPx);
632 } else {
633 padding.set(searchBarBounds.width(), edgeMarginPx,
634 hotseatBarHeightPx, edgeMarginPx);
635 }
Winson Chungb3800242013-10-24 11:01:54 -0700636 } else {
637 if (isTablet()) {
638 // Pad the left and right of the workspace to ensure consistent spacing
639 // between all icons
Winson Chung59a488a2013-12-10 12:32:14 -0800640 float gapScale = 1f + (dragViewScale - 1f) / 2f;
Winson Chungb3800242013-10-24 11:01:54 -0700641 int width = (orientation == CellLayout.LANDSCAPE)
642 ? Math.max(widthPx, heightPx)
643 : Math.min(widthPx, heightPx);
Winson Chung59a488a2013-12-10 12:32:14 -0800644 int height = (orientation != CellLayout.LANDSCAPE)
645 ? Math.max(widthPx, heightPx)
646 : Math.min(widthPx, heightPx);
647 int paddingTop = searchBarBounds.bottom;
648 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
649 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
650 (numColumns * gapScale * cellWidthPx)));
651 int availableHeight = Math.max(0, height - paddingTop - paddingBottom
652 - (int) (2 * numRows * cellHeightPx));
653 padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
654 availableWidth / 2, paddingBottom + availableHeight / 2);
Winson Chungb3800242013-10-24 11:01:54 -0700655 } else {
656 // Pad the top and bottom of the workspace with search/hotseat bar sizes
657 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
658 searchBarBounds.bottom,
659 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
660 hotseatBarHeightPx + pageIndicatorHeightPx);
661 }
662 }
663 return padding;
664 }
665
666 int getWorkspacePageSpacing(int orientation) {
Winson Chung59a488a2013-12-10 12:32:14 -0800667 if ((orientation == CellLayout.LANDSCAPE &&
668 transposeLayoutWithOrientation) || isLargeTablet()) {
Winson Chungb3800242013-10-24 11:01:54 -0700669 // In landscape mode the page spacing is set to the default.
670 return defaultPageSpacingPx;
671 } else {
672 // In portrait, we want the pages spaced such that there is no
673 // overhang of the previous / next page into the current page viewport.
674 // We assume symmetrical padding in portrait mode.
Adam Cohenefb31e32014-01-16 16:07:50 -0800675 return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
Winson Chungb3800242013-10-24 11:01:54 -0700676 }
677 }
678
679 Rect getOverviewModeButtonBarRect() {
680 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
681 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
682 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
683 return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
684 }
685
686 float getOverviewModeScale() {
687 Rect workspacePadding = getWorkspacePadding();
688 Rect overviewBar = getOverviewModeButtonBarRect();
689 int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
690 return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
691 }
692
693 // The rect returned will be extended to below the system ui that covers the workspace
694 Rect getHotseatRect() {
695 if (isVerticalBarLayout()) {
696 return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
697 Integer.MAX_VALUE, availableHeightPx);
698 } else {
699 return new Rect(0, availableHeightPx - hotseatBarHeightPx,
700 availableWidthPx, Integer.MAX_VALUE);
701 }
702 }
703
704 int calculateCellWidth(int width, int countX) {
705 return width / countX;
706 }
707 int calculateCellHeight(int height, int countY) {
708 return height / countY;
709 }
710
711 boolean isPhone() {
712 return !isTablet && !isLargeTablet;
713 }
714 boolean isTablet() {
715 return isTablet;
716 }
717 boolean isLargeTablet() {
718 return isLargeTablet;
719 }
720
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700721 /**
722 * When {@code true}, hotseat is on the bottom row when in landscape mode.
723 * If {@code false}, hotseat is on the right column when in landscape mode.
724 */
Winson Chungb3800242013-10-24 11:01:54 -0700725 boolean isVerticalBarLayout() {
726 return isLandscape && transposeLayoutWithOrientation;
727 }
728
729 boolean shouldFadeAdjacentWorkspaceScreens() {
730 return isVerticalBarLayout() || isLargeTablet();
731 }
732
Jorim Jaggid017f882014-01-14 17:08:48 -0800733 int getVisibleChildCount(ViewGroup parent) {
734 int visibleChildren = 0;
735 for (int i = 0; i < parent.getChildCount(); i++) {
736 if (parent.getChildAt(i).getVisibility() != View.GONE) {
737 visibleChildren++;
738 }
739 }
740 return visibleChildren;
741 }
742
Winson Chungb3800242013-10-24 11:01:54 -0700743 public void layout(Launcher launcher) {
744 FrameLayout.LayoutParams lp;
Winson Chungb3800242013-10-24 11:01:54 -0700745 boolean hasVerticalBarLayout = isVerticalBarLayout();
746
747 // Layout the search bar space
748 View searchBar = launcher.getSearchBar();
749 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
750 if (hasVerticalBarLayout) {
Winson Chung69e04ea2013-12-02 14:43:44 -0800751 // Vertical search bar space
Sunny Goyald9cc7802015-04-23 14:47:19 -0700752 lp.gravity = Gravity.LEFT;
Winson Chungb3800242013-10-24 11:01:54 -0700753 lp.width = searchBarSpaceHeightPx;
Adam Cohen24ce0b32014-01-14 16:18:14 -0800754
755 LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
756 targets.setOrientation(LinearLayout.VERTICAL);
Sunny Goyald9cc7802015-04-23 14:47:19 -0700757 FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams();
758 targetsLp.gravity = Gravity.TOP;
759 targetsLp.height = LayoutParams.WRAP_CONTENT;
760
Winson Chungb3800242013-10-24 11:01:54 -0700761 } else {
Winson Chung69e04ea2013-12-02 14:43:44 -0800762 // Horizontal search bar space
Sunny Goyald9cc7802015-04-23 14:47:19 -0700763 lp.gravity = Gravity.TOP;
Winson Chungb3800242013-10-24 11:01:54 -0700764 lp.height = searchBarSpaceHeightPx;
Sunny Goyald9cc7802015-04-23 14:47:19 -0700765
766 LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
767 targets.getLayoutParams().width = searchBarSpaceWidthPx;
Winson Chungb3800242013-10-24 11:01:54 -0700768 }
769 searchBar.setLayoutParams(lp);
770
Winson Chungb3800242013-10-24 11:01:54 -0700771 // Layout the workspace
772 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
773 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
774 lp.gravity = Gravity.CENTER;
775 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
776 Rect padding = getWorkspacePadding(orientation);
777 workspace.setLayoutParams(lp);
778 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
779 workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
780
781 // Layout the hotseat
782 View hotseat = launcher.findViewById(R.id.hotseat);
783 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
784 if (hasVerticalBarLayout) {
785 // Vertical hotseat
Winson Chung42b3c062013-12-04 12:09:59 -0800786 lp.gravity = Gravity.END;
Winson Chungb3800242013-10-24 11:01:54 -0700787 lp.width = hotseatBarHeightPx;
788 lp.height = LayoutParams.MATCH_PARENT;
789 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
790 } else if (isTablet()) {
Winson Chung59a488a2013-12-10 12:32:14 -0800791 // Pad the hotseat with the workspace padding calculated above
Winson Chungb3800242013-10-24 11:01:54 -0700792 lp.gravity = Gravity.BOTTOM;
793 lp.width = LayoutParams.MATCH_PARENT;
794 lp.height = hotseatBarHeightPx;
Winson Chung59a488a2013-12-10 12:32:14 -0800795 hotseat.setPadding(edgeMarginPx + padding.left, 0,
796 edgeMarginPx + padding.right,
Winson Chungb3800242013-10-24 11:01:54 -0700797 2 * edgeMarginPx);
798 } else {
799 // For phones, layout the hotseat without any bottom margin
800 // to ensure that we have space for the folders
801 lp.gravity = Gravity.BOTTOM;
802 lp.width = LayoutParams.MATCH_PARENT;
803 lp.height = hotseatBarHeightPx;
Sunny Goyale0bca382015-04-06 18:39:22 +0000804 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
805 2 * edgeMarginPx, 0);
Winson Chungb3800242013-10-24 11:01:54 -0700806 }
807 hotseat.setLayoutParams(lp);
808
809 // Layout the page indicators
810 View pageIndicator = launcher.findViewById(R.id.page_indicator);
811 if (pageIndicator != null) {
812 if (hasVerticalBarLayout) {
813 // Hide the page indicators when we have vertical search/hotseat
814 pageIndicator.setVisibility(View.GONE);
815 } else {
816 // Put the page indicators above the hotseat
817 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
818 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
819 lp.width = LayoutParams.WRAP_CONTENT;
820 lp.height = LayoutParams.WRAP_CONTENT;
821 lp.bottomMargin = hotseatBarHeightPx;
822 pageIndicator.setLayoutParams(lp);
823 }
824 }
825
Winson Chungb3800242013-10-24 11:01:54 -0700826 // Layout the Overview Mode
Jorim Jaggid017f882014-01-14 17:08:48 -0800827 ViewGroup overviewMode = launcher.getOverviewPanel();
Winson Chungb3800242013-10-24 11:01:54 -0700828 if (overviewMode != null) {
829 Rect r = getOverviewModeButtonBarRect();
830 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
831 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
Sunny Goyal8dfe2da2014-10-24 16:26:52 -0700832
833 int visibleChildCount = getVisibleChildCount(overviewMode);
834 int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
835 int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
836
837 lp.width = Math.min(availableWidthPx, maxWidth);
Winson Chungb3800242013-10-24 11:01:54 -0700838 lp.height = r.height();
839 overviewMode.setLayoutParams(lp);
Sunny Goyal8dfe2da2014-10-24 16:26:52 -0700840
841 if (lp.width > totalItemWidth && visibleChildCount > 1) {
842 // We have enough space. Lets add some margin too.
843 int margin = (lp.width - totalItemWidth) / (visibleChildCount-1);
844 View lastChild = null;
845
846 // Set margin of all visible children except the last visible child
847 for (int i = 0; i < visibleChildCount; i++) {
848 if (lastChild != null) {
849 MarginLayoutParams clp = (MarginLayoutParams) lastChild.getLayoutParams();
850 if (isLayoutRtl) {
851 clp.leftMargin = margin;
852 } else {
853 clp.rightMargin = margin;
854 }
855 lastChild.setLayoutParams(clp);
856 lastChild = null;
857 }
858 View thisChild = overviewMode.getChildAt(i);
859 if (thisChild.getVisibility() != View.GONE) {
860 lastChild = thisChild;
861 }
862 }
863 }
Winson Chungb3800242013-10-24 11:01:54 -0700864 }
865 }
866}