blob: 94589ad871c82ba53ffbf264bbb14922f647ab07 [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;
85 boolean isTablet;
86 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;
90
91 int desiredWorkspaceLeftRightMarginPx;
Adam Cohen59400422014-03-05 18:07:04 -080092 public int edgeMarginPx;
Winson Chungb3800242013-10-24 11:01:54 -070093 Rect defaultWidgetPadding;
94
95 int widthPx;
96 int heightPx;
Adam Cohen59400422014-03-05 18:07:04 -080097 public int availableWidthPx;
98 public int availableHeightPx;
Winson Chungb3800242013-10-24 11:01:54 -070099 int defaultPageSpacingPx;
100
101 int overviewModeMinIconZoneHeightPx;
102 int overviewModeMaxIconZoneHeightPx;
Jorim Jaggid017f882014-01-14 17:08:48 -0800103 int overviewModeBarItemWidthPx;
104 int overviewModeBarSpacerWidthPx;
Winson Chungb3800242013-10-24 11:01:54 -0700105 float overviewModeIconZoneRatio;
106 float overviewModeScaleFactor;
107
Adam Cohen59400422014-03-05 18:07:04 -0800108 public int cellWidthPx;
109 public int cellHeightPx;
110
Hyunyoung Song3f471442015-04-08 19:01:34 -0700111 public int iconSizePx;
112 public int iconTextSizePx;
Winson Chungb3800242013-10-24 11:01:54 -0700113 int iconDrawablePaddingPx;
Winson Chungb3800242013-10-24 11:01:54 -0700114 int allAppsIconSizePx;
115 int allAppsIconTextSizePx;
116 int allAppsCellWidthPx;
117 int allAppsCellHeightPx;
118 int allAppsCellPaddingPx;
119 int folderBackgroundOffset;
120 int folderIconSizePx;
121 int folderCellWidthPx;
122 int folderCellHeightPx;
123 int hotseatCellWidthPx;
124 int hotseatCellHeightPx;
125 int hotseatIconSizePx;
126 int hotseatBarHeightPx;
127 int hotseatAllAppsRank;
128 int allAppsNumRows;
129 int allAppsNumCols;
Winson Chung93f98ea2015-03-10 16:28:47 -0700130 int appsViewNumCols;
Winson Chungb3800242013-10-24 11:01:54 -0700131 int searchBarSpaceWidthPx;
Winson Chungb3800242013-10-24 11:01:54 -0700132 int searchBarSpaceHeightPx;
Winson Chungb3800242013-10-24 11:01:54 -0700133 int pageIndicatorHeightPx;
Adam Cohen63f1ec02014-08-12 09:23:13 -0700134 int allAppsButtonVisualSize;
Winson Chungb3800242013-10-24 11:01:54 -0700135
Winson Chung59a488a2013-12-10 12:32:14 -0800136 float dragViewScale;
137
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700138 int allAppsShortEdgeCount = -1;
139 int allAppsLongEdgeCount = -1;
140
Winson Chungb3800242013-10-24 11:01:54 -0700141 private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
142
Sunny Goyalfee35bb2015-05-11 11:38:19 -0700143 DeviceProfile(String n, float w, float h,
144 int r, int c, int fr, int fc,
145 float is, float its, float hs, float his, int dlId) {
Winson Chungb3800242013-10-24 11:01:54 -0700146 // Ensure that we have an odd number of hotseat items (since we need to place all apps)
Sunny Goyalc9acdd52015-02-26 12:34:42 -0800147 if (hs % 2 == 0) {
Winson Chungb3800242013-10-24 11:01:54 -0700148 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
149 }
150
151 name = n;
152 minWidthDps = w;
153 minHeightDps = h;
Sunny Goyalfee35bb2015-05-11 11:38:19 -0700154
Winson Chungb3800242013-10-24 11:01:54 -0700155 numRows = r;
156 numColumns = c;
Sunny Goyalfee35bb2015-05-11 11:38:19 -0700157 numFolderRows = fr;
158 numFolderColumns = fc;
159
Winson Chungb3800242013-10-24 11:01:54 -0700160 iconSize = is;
161 iconTextSize = its;
162 numHotseatIcons = hs;
163 hotseatIconSize = his;
Winson Chungbe876472014-05-14 14:15:20 -0700164 defaultLayoutId = dlId;
Winson Chungb3800242013-10-24 11:01:54 -0700165 }
166
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700167 DeviceProfile() {
168 }
169
Winson Chungb3800242013-10-24 11:01:54 -0700170 DeviceProfile(Context context,
171 ArrayList<DeviceProfile> profiles,
172 float minWidth, float minHeight,
173 int wPx, int hPx,
174 int awPx, int ahPx,
175 Resources res) {
176 DisplayMetrics dm = res.getDisplayMetrics();
177 ArrayList<DeviceProfileQuery> points =
178 new ArrayList<DeviceProfileQuery>();
179 transposeLayoutWithOrientation =
180 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
181 minWidthDps = minWidth;
182 minHeightDps = minHeight;
183
184 ComponentName cn = new ComponentName(context.getPackageName(),
185 this.getClass().getName());
186 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
187 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
188 desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
189 pageIndicatorHeightPx =
190 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
191 defaultPageSpacingPx =
192 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
193 allAppsCellPaddingPx =
194 res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
195 overviewModeMinIconZoneHeightPx =
196 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
197 overviewModeMaxIconZoneHeightPx =
198 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
Jorim Jaggid017f882014-01-14 17:08:48 -0800199 overviewModeBarItemWidthPx =
200 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
201 overviewModeBarSpacerWidthPx =
202 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
Winson Chungb3800242013-10-24 11:01:54 -0700203 overviewModeIconZoneRatio =
204 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
205 overviewModeScaleFactor =
206 res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
207
Winson Chungbe876472014-05-14 14:15:20 -0700208 // Find the closes profile given the width/height
Winson Chungb3800242013-10-24 11:01:54 -0700209 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700210 points.add(new DeviceProfileQuery(p, 0f));
Winson Chungb3800242013-10-24 11:01:54 -0700211 }
Winson Chungbe876472014-05-14 14:15:20 -0700212 DeviceProfile closestProfile = findClosestDeviceProfile(minWidth, minHeight, points);
213
214 // Snap to the closest row count
215 numRows = closestProfile.numRows;
216
217 // Snap to the closest column count
218 numColumns = closestProfile.numColumns;
219
Sunny Goyalfee35bb2015-05-11 11:38:19 -0700220 numFolderRows = closestProfile.numFolderRows;
221 numFolderColumns = closestProfile.numFolderColumns;
222
Winson Chungbe876472014-05-14 14:15:20 -0700223 // Snap to the closest hotseat size
224 numHotseatIcons = closestProfile.numHotseatIcons;
Winson Chungb3800242013-10-24 11:01:54 -0700225 hotseatAllAppsRank = (int) (numHotseatIcons / 2);
226
Winson Chungbe876472014-05-14 14:15:20 -0700227 // Snap to the closest default layout id
228 defaultLayoutId = closestProfile.defaultLayoutId;
229
Winson Chungb3800242013-10-24 11:01:54 -0700230 // Interpolate the icon size
231 points.clear();
232 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700233 points.add(new DeviceProfileQuery(p, p.iconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700234 }
235 iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700236
Winson Chungb3800242013-10-24 11:01:54 -0700237 // AllApps uses the original non-scaled icon size
238 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
239
240 // Interpolate the icon text size
241 points.clear();
242 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700243 points.add(new DeviceProfileQuery(p, p.iconTextSize));
Winson Chungb3800242013-10-24 11:01:54 -0700244 }
245 iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
246 iconDrawablePaddingOriginalPx =
247 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
248 // AllApps uses the original non-scaled icon text size
249 allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
250
251 // Interpolate the hotseat icon size
252 points.clear();
253 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700254 points.add(new DeviceProfileQuery(p, p.hotseatIconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700255 }
256 // Hotseat
257 hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
258
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700259 // If the partner customization apk contains any grid overrides, apply them
260 applyPartnerDeviceProfileOverrides(context, dm);
261
Winson Chungb3800242013-10-24 11:01:54 -0700262 // Calculate the remaining vars
263 updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
264 updateAvailableDimensions(context);
Adam Cohen63f1ec02014-08-12 09:23:13 -0700265 computeAllAppsButtonSize(context);
266 }
267
268 /**
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700269 * Apply any Partner customization grid overrides.
270 *
271 * Currently we support: all apps row / column count.
272 */
273 private void applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm) {
274 Partner p = Partner.get(ctx.getPackageManager());
275 if (p != null) {
276 DeviceProfile partnerDp = p.getDeviceProfileOverride(dm);
277 if (partnerDp != null) {
278 if (partnerDp.numRows > 0 && partnerDp.numColumns > 0) {
Sunny Goyalfee35bb2015-05-11 11:38:19 -0700279 numRows = numFolderRows = partnerDp.numRows;
280 numColumns = numFolderColumns = partnerDp.numColumns;
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700281 }
282 if (partnerDp.allAppsShortEdgeCount > 0 && partnerDp.allAppsLongEdgeCount > 0) {
283 allAppsShortEdgeCount = partnerDp.allAppsShortEdgeCount;
284 allAppsLongEdgeCount = partnerDp.allAppsLongEdgeCount;
285 }
286 if (partnerDp.iconSize > 0) {
287 iconSize = partnerDp.iconSize;
288 // AllApps uses the original non-scaled icon size
289 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
290 }
291 }
292 }
293 }
294
295 /**
Adam Cohen63f1ec02014-08-12 09:23:13 -0700296 * Determine the exact visual footprint of the all apps button, taking into account scaling
297 * and internal padding of the drawable.
298 */
299 private void computeAllAppsButtonSize(Context context) {
300 Resources res = context.getResources();
301 float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
302 LauncherAppState app = LauncherAppState.getInstance();
303 allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding));
Winson Chungb3800242013-10-24 11:01:54 -0700304 }
305
306 void addCallback(DeviceProfileCallbacks cb) {
307 mCallbacks.add(cb);
308 cb.onAvailableSizeChanged(this);
309 }
310 void removeCallback(DeviceProfileCallbacks cb) {
311 mCallbacks.remove(cb);
312 }
313
314 private int getDeviceOrientation(Context context) {
315 WindowManager windowManager = (WindowManager)
316 context.getSystemService(Context.WINDOW_SERVICE);
317 Resources resources = context.getResources();
318 DisplayMetrics dm = resources.getDisplayMetrics();
319 Configuration config = resources.getConfiguration();
320 int rotation = windowManager.getDefaultDisplay().getRotation();
321
322 boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
323 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
324 boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
325 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
326 if (isLandscape || isRotatedPortrait) {
327 return CellLayout.LANDSCAPE;
328 } else {
329 return CellLayout.PORTRAIT;
330 }
331 }
332
333 private void updateAvailableDimensions(Context context) {
334 WindowManager windowManager = (WindowManager)
335 context.getSystemService(Context.WINDOW_SERVICE);
336 Display display = windowManager.getDefaultDisplay();
337 Resources resources = context.getResources();
338 DisplayMetrics dm = resources.getDisplayMetrics();
339 Configuration config = resources.getConfiguration();
340
341 // There are three possible configurations that the dynamic grid accounts for, portrait,
342 // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
343 // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
344 // the height is the smallest height (either with the nav bar at the bottom or to the
345 // side) and otherwise, the height is simply the largest possible height for a portrait
346 // device.
347 Point size = new Point();
348 Point smallestSize = new Point();
349 Point largestSize = new Point();
350 display.getSize(size);
351 display.getCurrentSizeRange(smallestSize, largestSize);
352 availableWidthPx = size.x;
353 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
354 availableHeightPx = smallestSize.y;
355 } else {
356 availableHeightPx = largestSize.y;
357 }
358
359 // Check to see if the icons fit in the new available height. If not, then we need to
360 // shrink the icon size.
Winson Chungb3800242013-10-24 11:01:54 -0700361 float scale = 1f;
362 int drawablePadding = iconDrawablePaddingOriginalPx;
363 updateIconSize(1f, drawablePadding, resources, dm);
364 float usedHeight = (cellHeightPx * numRows);
Winson Chung59a488a2013-12-10 12:32:14 -0800365
366 Rect workspacePadding = getWorkspacePadding();
Winson Chungb3800242013-10-24 11:01:54 -0700367 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
368 if (usedHeight > maxHeight) {
369 scale = maxHeight / usedHeight;
370 drawablePadding = 0;
371 }
372 updateIconSize(scale, drawablePadding, resources, dm);
373
374 // Make the callbacks
375 for (DeviceProfileCallbacks cb : mCallbacks) {
376 cb.onAvailableSizeChanged(this);
377 }
378 }
379
Winson Chung93f98ea2015-03-10 16:28:47 -0700380 private void updateIconSize(float scale, int drawablePadding, Resources res,
Winson Chungb3800242013-10-24 11:01:54 -0700381 DisplayMetrics dm) {
382 iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
383 iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
384 iconDrawablePaddingPx = drawablePadding;
385 hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
386
387 // Search Bar
Sunny Goyal594d76d2014-11-06 10:12:54 -0800388 searchBarSpaceWidthPx = Math.min(widthPx,
Winson Chung93f98ea2015-03-10 16:28:47 -0700389 res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
Sunny Goyal594d76d2014-11-06 10:12:54 -0800390 searchBarSpaceHeightPx = getSearchBarTopOffset()
Winson Chung93f98ea2015-03-10 16:28:47 -0700391 + res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
Winson Chungb3800242013-10-24 11:01:54 -0700392
393 // Calculate the actual text height
394 Paint textPaint = new Paint();
395 textPaint.setTextSize(iconTextSizePx);
396 FontMetrics fm = textPaint.getFontMetrics();
397 cellWidthPx = iconSizePx;
398 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
Winson Chung93f98ea2015-03-10 16:28:47 -0700399 final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
Winson Chung59a488a2013-12-10 12:32:14 -0800400 dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
Winson Chungb3800242013-10-24 11:01:54 -0700401
402 // Hotseat
403 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
404 hotseatCellWidthPx = iconSizePx;
405 hotseatCellHeightPx = iconSizePx;
406
407 // Folder
408 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
409 folderCellHeightPx = cellHeightPx + edgeMarginPx;
410 folderBackgroundOffset = -edgeMarginPx;
411 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
412
413 // All Apps
Winson Chungb3800242013-10-24 11:01:54 -0700414 allAppsCellWidthPx = allAppsIconSizePx;
415 allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
416 int maxLongEdgeCellCount =
Winson Chung93f98ea2015-03-10 16:28:47 -0700417 res.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
Winson Chungb3800242013-10-24 11:01:54 -0700418 int maxShortEdgeCellCount =
Winson Chung93f98ea2015-03-10 16:28:47 -0700419 res.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
Winson Chungb3800242013-10-24 11:01:54 -0700420 int minEdgeCellCount =
Winson Chung93f98ea2015-03-10 16:28:47 -0700421 res.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
Winson Chungb3800242013-10-24 11:01:54 -0700422 int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
423 int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
424
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700425 if (allAppsShortEdgeCount > 0 && allAppsLongEdgeCount > 0) {
426 allAppsNumRows = isLandscape ? allAppsShortEdgeCount : allAppsLongEdgeCount;
427 allAppsNumCols = isLandscape ? allAppsLongEdgeCount : allAppsShortEdgeCount;
428 } else {
429 allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
430 (allAppsCellHeightPx + allAppsCellPaddingPx);
431 allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
Winson Chung93f98ea2015-03-10 16:28:47 -0700432 allAppsNumCols = (availableWidthPx) / (allAppsCellWidthPx + allAppsCellPaddingPx);
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700433 allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
434 }
Winson Chung93f98ea2015-03-10 16:28:47 -0700435
Winson Chung0f785722015-04-08 10:27:49 -0700436 int appsContainerViewWidthPx = res.getDimensionPixelSize(R.dimen.apps_container_width);
437 updateAppsViewNumCols(res, appsContainerViewWidthPx);
438 }
439
440 public boolean updateAppsViewNumCols(Resources res, int containerWidth) {
Winson Chung83f59ab2015-05-05 17:21:58 -0700441 if (AppsContainerView.GRID_HIDE_SECTION_HEADERS) {
442 if (appsViewNumCols != allAppsNumCols) {
443 appsViewNumCols = allAppsNumCols;
444 return true;
445 }
446 return false;
447 }
Winson Chung93f98ea2015-03-10 16:28:47 -0700448 int appsViewLeftMarginPx =
449 res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
Winson Chung0f785722015-04-08 10:27:49 -0700450 int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx;
451 int numCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
Winson Chungbd808532015-04-08 11:07:53 -0700452 (allAppsCellWidthPx + 2 * allAppsCellPaddingPx);
Winson Chung0f785722015-04-08 10:27:49 -0700453 if (numCols != appsViewNumCols) {
454 appsViewNumCols = numCols;
455 return true;
456 }
457 return false;
Winson Chungb3800242013-10-24 11:01:54 -0700458 }
459
460 void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
461 int awPx, int ahPx) {
Winson Chung42b3c062013-12-04 12:09:59 -0800462 Configuration configuration = resources.getConfiguration();
463 isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
Winson Chungb3800242013-10-24 11:01:54 -0700464 isTablet = resources.getBoolean(R.bool.is_tablet);
465 isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
Winson Chung6033ceb2014-02-05 12:37:42 -0800466 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
467 isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
468 } else {
469 isLayoutRtl = false;
470 }
Winson Chungb3800242013-10-24 11:01:54 -0700471 widthPx = wPx;
472 heightPx = hPx;
473 availableWidthPx = awPx;
474 availableHeightPx = ahPx;
475
476 updateAvailableDimensions(context);
477 }
478
Adam Cohen091440a2015-03-18 14:16:05 -0700479 @Thunk float dist(PointF p0, PointF p1) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700480 return (float) Math.hypot(p1.x - p0.x, p1.y - p0.y);
Winson Chungb3800242013-10-24 11:01:54 -0700481 }
482
483 private float weight(PointF a, PointF b,
484 float pow) {
485 float d = dist(a, b);
486 if (d == 0f) {
487 return Float.POSITIVE_INFINITY;
488 }
489 return (float) (1f / Math.pow(d, pow));
490 }
491
Winson Chungbe876472014-05-14 14:15:20 -0700492 /** Returns the closest device profile given the width and height and a list of profiles */
493 private DeviceProfile findClosestDeviceProfile(float width, float height,
494 ArrayList<DeviceProfileQuery> points) {
495 return findClosestDeviceProfiles(width, height, points).get(0).profile;
496 }
497
498 /** Returns the closest device profiles ordered by closeness to the specified width and height */
499 private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height,
500 ArrayList<DeviceProfileQuery> points) {
501 final PointF xy = new PointF(width, height);
502
503 // Sort the profiles by their closeness to the dimensions
504 ArrayList<DeviceProfileQuery> pointsByNearness = points;
505 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
506 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
507 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
508 }
509 });
510
511 return pointsByNearness;
512 }
513
Winson Chungb3800242013-10-24 11:01:54 -0700514 private float invDistWeightedInterpolate(float width, float height,
515 ArrayList<DeviceProfileQuery> points) {
516 float sum = 0;
517 float weights = 0;
518 float pow = 5;
519 float kNearestNeighbors = 3;
520 final PointF xy = new PointF(width, height);
521
Winson Chungbe876472014-05-14 14:15:20 -0700522 ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height,
523 points);
Winson Chungb3800242013-10-24 11:01:54 -0700524
525 for (int i = 0; i < pointsByNearness.size(); ++i) {
526 DeviceProfileQuery p = pointsByNearness.get(i);
527 if (i < kNearestNeighbors) {
528 float w = weight(xy, p.dimens, pow);
529 if (w == Float.POSITIVE_INFINITY) {
530 return p.value;
531 }
532 weights += w;
533 }
534 }
535
536 for (int i = 0; i < pointsByNearness.size(); ++i) {
537 DeviceProfileQuery p = pointsByNearness.get(i);
538 if (i < kNearestNeighbors) {
539 float w = weight(xy, p.dimens, pow);
540 sum += w * p.value / weights;
541 }
542 }
543
544 return sum;
545 }
546
Winson Chung69e04ea2013-12-02 14:43:44 -0800547 /** Returns the search bar top offset */
548 int getSearchBarTopOffset() {
549 if (isTablet() && !isVerticalBarLayout()) {
550 return 4 * edgeMarginPx;
551 } else {
552 return 2 * edgeMarginPx;
553 }
554 }
555
Winson Chungb3800242013-10-24 11:01:54 -0700556 /** Returns the search bar bounds in the current orientation */
557 Rect getSearchBarBounds() {
558 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
559 }
560 /** Returns the search bar bounds in the specified orientation */
561 Rect getSearchBarBounds(int orientation) {
562 Rect bounds = new Rect();
563 if (orientation == CellLayout.LANDSCAPE &&
564 transposeLayoutWithOrientation) {
Winson Chung42b3c062013-12-04 12:09:59 -0800565 if (isLayoutRtl) {
566 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
567 availableWidthPx, availableHeightPx - edgeMarginPx);
568 } else {
569 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
570 availableHeightPx - edgeMarginPx);
571 }
Winson Chungb3800242013-10-24 11:01:54 -0700572 } else {
573 if (isTablet()) {
574 // Pad the left and right of the workspace to ensure consistent spacing
575 // between all icons
576 int width = (orientation == CellLayout.LANDSCAPE)
577 ? Math.max(widthPx, heightPx)
578 : Math.min(widthPx, heightPx);
579 // XXX: If the icon size changes across orientations, we will have to take
580 // that into account here too.
581 int gap = (int) ((width - 2 * edgeMarginPx -
582 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
Winson Chung2cb24712013-12-02 15:00:39 -0800583 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
584 availableWidthPx - (edgeMarginPx + gap),
Winson Chungb3800242013-10-24 11:01:54 -0700585 searchBarSpaceHeightPx);
586 } else {
Winson Chung2cb24712013-12-02 15:00:39 -0800587 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
588 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700589 availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
590 defaultWidgetPadding.right), searchBarSpaceHeightPx);
591 }
592 }
593 return bounds;
594 }
595
Winson Chunga6945242014-01-08 14:04:34 -0800596 /** Returns the bounds of the workspace page indicators. */
597 Rect getWorkspacePageIndicatorBounds(Rect insets) {
598 Rect workspacePadding = getWorkspacePadding();
Winson Chung205cd772014-01-15 14:31:59 -0800599 if (isLandscape && transposeLayoutWithOrientation) {
600 if (isLayoutRtl) {
601 return new Rect(workspacePadding.left, workspacePadding.top,
602 workspacePadding.left + pageIndicatorHeightPx,
603 heightPx - workspacePadding.bottom - insets.bottom);
604 } else {
605 int pageIndicatorLeft = widthPx - workspacePadding.right;
606 return new Rect(pageIndicatorLeft, workspacePadding.top,
607 pageIndicatorLeft + pageIndicatorHeightPx,
608 heightPx - workspacePadding.bottom - insets.bottom);
609 }
610 } else {
611 int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom;
612 return new Rect(workspacePadding.left, pageIndicatorTop,
613 widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx);
614 }
Winson Chunga6945242014-01-08 14:04:34 -0800615 }
616
Adam Cohen59400422014-03-05 18:07:04 -0800617 public int getWorkspaceGridHeight() {
618 Rect p = getWorkspacePadding();
619 return availableHeightPx - p.top - p.bottom;
620 }
621
622 public int getWorkspaceGridWidth() {
623 Rect p = getWorkspacePadding();
624 return availableWidthPx - p.left - p.right;
625 }
626
Winson Chungb3800242013-10-24 11:01:54 -0700627 /** Returns the workspace padding in the specified orientation */
628 Rect getWorkspacePadding() {
629 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
630 }
631 Rect getWorkspacePadding(int orientation) {
632 Rect searchBarBounds = getSearchBarBounds(orientation);
633 Rect padding = new Rect();
634 if (orientation == CellLayout.LANDSCAPE &&
635 transposeLayoutWithOrientation) {
636 // Pad the left and right of the workspace with search/hotseat bar sizes
Winson Chung42b3c062013-12-04 12:09:59 -0800637 if (isLayoutRtl) {
638 padding.set(hotseatBarHeightPx, edgeMarginPx,
639 searchBarBounds.width(), edgeMarginPx);
640 } else {
641 padding.set(searchBarBounds.width(), edgeMarginPx,
642 hotseatBarHeightPx, edgeMarginPx);
643 }
Winson Chungb3800242013-10-24 11:01:54 -0700644 } else {
645 if (isTablet()) {
646 // Pad the left and right of the workspace to ensure consistent spacing
647 // between all icons
Winson Chung59a488a2013-12-10 12:32:14 -0800648 float gapScale = 1f + (dragViewScale - 1f) / 2f;
Winson Chungb3800242013-10-24 11:01:54 -0700649 int width = (orientation == CellLayout.LANDSCAPE)
650 ? Math.max(widthPx, heightPx)
651 : Math.min(widthPx, heightPx);
Winson Chung59a488a2013-12-10 12:32:14 -0800652 int height = (orientation != CellLayout.LANDSCAPE)
653 ? Math.max(widthPx, heightPx)
654 : Math.min(widthPx, heightPx);
655 int paddingTop = searchBarBounds.bottom;
656 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
657 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
658 (numColumns * gapScale * cellWidthPx)));
659 int availableHeight = Math.max(0, height - paddingTop - paddingBottom
660 - (int) (2 * numRows * cellHeightPx));
661 padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
662 availableWidth / 2, paddingBottom + availableHeight / 2);
Winson Chungb3800242013-10-24 11:01:54 -0700663 } else {
664 // Pad the top and bottom of the workspace with search/hotseat bar sizes
665 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
666 searchBarBounds.bottom,
667 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
668 hotseatBarHeightPx + pageIndicatorHeightPx);
669 }
670 }
671 return padding;
672 }
673
674 int getWorkspacePageSpacing(int orientation) {
Winson Chung59a488a2013-12-10 12:32:14 -0800675 if ((orientation == CellLayout.LANDSCAPE &&
676 transposeLayoutWithOrientation) || isLargeTablet()) {
Winson Chungb3800242013-10-24 11:01:54 -0700677 // In landscape mode the page spacing is set to the default.
678 return defaultPageSpacingPx;
679 } else {
680 // In portrait, we want the pages spaced such that there is no
681 // overhang of the previous / next page into the current page viewport.
682 // We assume symmetrical padding in portrait mode.
Adam Cohenefb31e32014-01-16 16:07:50 -0800683 return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
Winson Chungb3800242013-10-24 11:01:54 -0700684 }
685 }
686
687 Rect getOverviewModeButtonBarRect() {
688 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
689 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
690 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
691 return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
692 }
693
694 float getOverviewModeScale() {
695 Rect workspacePadding = getWorkspacePadding();
696 Rect overviewBar = getOverviewModeButtonBarRect();
697 int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
698 return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
699 }
700
701 // The rect returned will be extended to below the system ui that covers the workspace
702 Rect getHotseatRect() {
703 if (isVerticalBarLayout()) {
704 return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
705 Integer.MAX_VALUE, availableHeightPx);
706 } else {
707 return new Rect(0, availableHeightPx - hotseatBarHeightPx,
708 availableWidthPx, Integer.MAX_VALUE);
709 }
710 }
711
712 int calculateCellWidth(int width, int countX) {
713 return width / countX;
714 }
715 int calculateCellHeight(int height, int countY) {
716 return height / countY;
717 }
718
719 boolean isPhone() {
720 return !isTablet && !isLargeTablet;
721 }
722 boolean isTablet() {
723 return isTablet;
724 }
725 boolean isLargeTablet() {
726 return isLargeTablet;
727 }
728
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700729 /**
730 * When {@code true}, hotseat is on the bottom row when in landscape mode.
731 * If {@code false}, hotseat is on the right column when in landscape mode.
732 */
Winson Chungb3800242013-10-24 11:01:54 -0700733 boolean isVerticalBarLayout() {
734 return isLandscape && transposeLayoutWithOrientation;
735 }
736
737 boolean shouldFadeAdjacentWorkspaceScreens() {
738 return isVerticalBarLayout() || isLargeTablet();
739 }
740
Jorim Jaggid017f882014-01-14 17:08:48 -0800741 int getVisibleChildCount(ViewGroup parent) {
742 int visibleChildren = 0;
743 for (int i = 0; i < parent.getChildCount(); i++) {
744 if (parent.getChildAt(i).getVisibility() != View.GONE) {
745 visibleChildren++;
746 }
747 }
748 return visibleChildren;
749 }
750
Winson Chungb3800242013-10-24 11:01:54 -0700751 public void layout(Launcher launcher) {
752 FrameLayout.LayoutParams lp;
Winson Chungb3800242013-10-24 11:01:54 -0700753 boolean hasVerticalBarLayout = isVerticalBarLayout();
754
755 // Layout the search bar space
756 View searchBar = launcher.getSearchBar();
757 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
758 if (hasVerticalBarLayout) {
Winson Chung69e04ea2013-12-02 14:43:44 -0800759 // Vertical search bar space
Sunny Goyald9cc7802015-04-23 14:47:19 -0700760 lp.gravity = Gravity.LEFT;
Winson Chungb3800242013-10-24 11:01:54 -0700761 lp.width = searchBarSpaceHeightPx;
Adam Cohen24ce0b32014-01-14 16:18:14 -0800762
763 LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
764 targets.setOrientation(LinearLayout.VERTICAL);
Sunny Goyald9cc7802015-04-23 14:47:19 -0700765 FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams();
766 targetsLp.gravity = Gravity.TOP;
767 targetsLp.height = LayoutParams.WRAP_CONTENT;
768
Winson Chungb3800242013-10-24 11:01:54 -0700769 } else {
Winson Chung69e04ea2013-12-02 14:43:44 -0800770 // Horizontal search bar space
Sunny Goyald9cc7802015-04-23 14:47:19 -0700771 lp.gravity = Gravity.TOP;
Winson Chungb3800242013-10-24 11:01:54 -0700772 lp.height = searchBarSpaceHeightPx;
Sunny Goyald9cc7802015-04-23 14:47:19 -0700773
774 LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
775 targets.getLayoutParams().width = searchBarSpaceWidthPx;
Winson Chungb3800242013-10-24 11:01:54 -0700776 }
777 searchBar.setLayoutParams(lp);
778
Winson Chungb3800242013-10-24 11:01:54 -0700779 // Layout the workspace
780 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
781 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
782 lp.gravity = Gravity.CENTER;
783 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
784 Rect padding = getWorkspacePadding(orientation);
785 workspace.setLayoutParams(lp);
786 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
787 workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
788
789 // Layout the hotseat
790 View hotseat = launcher.findViewById(R.id.hotseat);
791 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
792 if (hasVerticalBarLayout) {
793 // Vertical hotseat
Winson Chung42b3c062013-12-04 12:09:59 -0800794 lp.gravity = Gravity.END;
Winson Chungb3800242013-10-24 11:01:54 -0700795 lp.width = hotseatBarHeightPx;
796 lp.height = LayoutParams.MATCH_PARENT;
797 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
798 } else if (isTablet()) {
Winson Chung59a488a2013-12-10 12:32:14 -0800799 // Pad the hotseat with the workspace padding calculated above
Winson Chungb3800242013-10-24 11:01:54 -0700800 lp.gravity = Gravity.BOTTOM;
801 lp.width = LayoutParams.MATCH_PARENT;
802 lp.height = hotseatBarHeightPx;
Winson Chung59a488a2013-12-10 12:32:14 -0800803 hotseat.setPadding(edgeMarginPx + padding.left, 0,
804 edgeMarginPx + padding.right,
Winson Chungb3800242013-10-24 11:01:54 -0700805 2 * edgeMarginPx);
806 } else {
807 // For phones, layout the hotseat without any bottom margin
808 // to ensure that we have space for the folders
809 lp.gravity = Gravity.BOTTOM;
810 lp.width = LayoutParams.MATCH_PARENT;
811 lp.height = hotseatBarHeightPx;
Sunny Goyale0bca382015-04-06 18:39:22 +0000812 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
813 2 * edgeMarginPx, 0);
Winson Chungb3800242013-10-24 11:01:54 -0700814 }
815 hotseat.setLayoutParams(lp);
816
817 // Layout the page indicators
818 View pageIndicator = launcher.findViewById(R.id.page_indicator);
819 if (pageIndicator != null) {
820 if (hasVerticalBarLayout) {
821 // Hide the page indicators when we have vertical search/hotseat
822 pageIndicator.setVisibility(View.GONE);
823 } else {
824 // Put the page indicators above the hotseat
825 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
826 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
827 lp.width = LayoutParams.WRAP_CONTENT;
828 lp.height = LayoutParams.WRAP_CONTENT;
829 lp.bottomMargin = hotseatBarHeightPx;
830 pageIndicator.setLayoutParams(lp);
831 }
832 }
833
Winson Chungb3800242013-10-24 11:01:54 -0700834 // Layout the Overview Mode
Jorim Jaggid017f882014-01-14 17:08:48 -0800835 ViewGroup overviewMode = launcher.getOverviewPanel();
Winson Chungb3800242013-10-24 11:01:54 -0700836 if (overviewMode != null) {
837 Rect r = getOverviewModeButtonBarRect();
838 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
839 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
Sunny Goyal8dfe2da2014-10-24 16:26:52 -0700840
841 int visibleChildCount = getVisibleChildCount(overviewMode);
842 int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
843 int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
844
845 lp.width = Math.min(availableWidthPx, maxWidth);
Winson Chungb3800242013-10-24 11:01:54 -0700846 lp.height = r.height();
847 overviewMode.setLayoutParams(lp);
Sunny Goyal8dfe2da2014-10-24 16:26:52 -0700848
849 if (lp.width > totalItemWidth && visibleChildCount > 1) {
850 // We have enough space. Lets add some margin too.
851 int margin = (lp.width - totalItemWidth) / (visibleChildCount-1);
852 View lastChild = null;
853
854 // Set margin of all visible children except the last visible child
855 for (int i = 0; i < visibleChildCount; i++) {
856 if (lastChild != null) {
857 MarginLayoutParams clp = (MarginLayoutParams) lastChild.getLayoutParams();
858 if (isLayoutRtl) {
859 clp.leftMargin = margin;
860 } else {
861 clp.rightMargin = margin;
862 }
863 lastChild.setLayoutParams(clp);
864 lastChild = null;
865 }
866 View thisChild = overviewMode.getChildAt(i);
867 if (thisChild.getVisibility() != View.GONE) {
868 lastChild = thisChild;
869 }
870 }
871 }
Winson Chungb3800242013-10-24 11:01:54 -0700872 }
873 }
874}