blob: 9b3b193f31b1deca5b676aaab1bc561cd38936cf [file] [log] [blame]
Winson Chung5f8afe62013-08-12 16:19:28 -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
Winson Chungf7d45852013-10-10 18:57:15 -070019import android.appwidget.AppWidgetHostView;
20import android.content.ComponentName;
21import android.content.Context;
Winson Chung5f8afe62013-08-12 16:19:28 -070022import android.content.res.Configuration;
23import android.content.res.Resources;
24import android.graphics.Paint;
Winson Chung5f8afe62013-08-12 16:19:28 -070025import android.graphics.Paint.FontMetrics;
Winson Chung6e1c0d32013-10-25 15:24:24 -070026import android.graphics.Point;
Winson Chungf7d45852013-10-10 18:57:15 -070027import android.graphics.PointF;
Winson Chung5f8afe62013-08-12 16:19:28 -070028import android.graphics.Rect;
29import android.util.DisplayMetrics;
30import android.util.TypedValue;
Winson Chung6e1c0d32013-10-25 15:24:24 -070031import android.view.Display;
Winson Chung5f8afe62013-08-12 16:19:28 -070032import android.view.Gravity;
Winson Chung6e1c0d32013-10-25 15:24:24 -070033import android.view.Surface;
Winson Chung5f8afe62013-08-12 16:19:28 -070034import android.view.View;
35import android.view.ViewGroup.LayoutParams;
Winson Chung6e1c0d32013-10-25 15:24:24 -070036import android.view.WindowManager;
Winson Chung5f8afe62013-08-12 16:19:28 -070037import android.widget.FrameLayout;
38
39import java.util.ArrayList;
40import java.util.Collections;
41import java.util.Comparator;
42
43
44class DeviceProfileQuery {
45 float widthDps;
46 float heightDps;
47 float value;
48 PointF dimens;
49
50 DeviceProfileQuery(float w, float h, float v) {
51 widthDps = w;
52 heightDps = h;
53 value = v;
54 dimens = new PointF(w, h);
55 }
56}
57
58class DeviceProfile {
Winson Chung6e1c0d32013-10-25 15:24:24 -070059 public static interface DeviceProfileCallbacks {
60 public void onAvailableSizeChanged(DeviceProfile grid);
61 }
62
Winson Chung5f8afe62013-08-12 16:19:28 -070063 String name;
64 float minWidthDps;
65 float minHeightDps;
66 float numRows;
67 float numColumns;
Winson Chung5f8afe62013-08-12 16:19:28 -070068 float numHotseatIcons;
Winson Chung6e1c0d32013-10-25 15:24:24 -070069 private float iconSize;
70 private float iconTextSize;
71 private int iconDrawablePaddingOriginalPx;
72 private float hotseatIconSize;
Winson Chung5f8afe62013-08-12 16:19:28 -070073
74 boolean isLandscape;
75 boolean isTablet;
76 boolean isLargeTablet;
77 boolean transposeLayoutWithOrientation;
78
Winson Chungf7d45852013-10-10 18:57:15 -070079 int desiredWorkspaceLeftRightMarginPx;
Winson Chung5f8afe62013-08-12 16:19:28 -070080 int edgeMarginPx;
Winson Chungf7d45852013-10-10 18:57:15 -070081 Rect defaultWidgetPadding;
Winson Chung5f8afe62013-08-12 16:19:28 -070082
83 int widthPx;
84 int heightPx;
Winson Chung892c74d2013-08-22 16:15:50 -070085 int availableWidthPx;
86 int availableHeightPx;
Adam Cohen3b185e22013-10-29 14:45:58 -070087 int defaultPageSpacingPx;
Winson Chung6e1c0d32013-10-25 15:24:24 -070088
Winson Chung5f8afe62013-08-12 16:19:28 -070089 int iconSizePx;
90 int iconTextSizePx;
Winson Chung6e1c0d32013-10-25 15:24:24 -070091 int iconDrawablePaddingPx;
Winson Chung5f8afe62013-08-12 16:19:28 -070092 int cellWidthPx;
93 int cellHeightPx;
Winson Chung67ca7e42013-10-31 16:53:19 -070094 int allAppsIconSizePx;
95 int allAppsIconTextSizePx;
96 int allAppsCellWidthPx;
97 int allAppsCellHeightPx;
98 int allAppsCellPaddingPx;
Winson Chung5f8afe62013-08-12 16:19:28 -070099 int folderBackgroundOffset;
100 int folderIconSizePx;
101 int folderCellWidthPx;
102 int folderCellHeightPx;
103 int hotseatCellWidthPx;
104 int hotseatCellHeightPx;
105 int hotseatIconSizePx;
106 int hotseatBarHeightPx;
Winson Chungc58497e2013-09-03 17:48:37 -0700107 int hotseatAllAppsRank;
108 int allAppsNumRows;
109 int allAppsNumCols;
Winson Chung5f8afe62013-08-12 16:19:28 -0700110 int searchBarSpaceWidthPx;
111 int searchBarSpaceMaxWidthPx;
112 int searchBarSpaceHeightPx;
113 int searchBarHeightPx;
114 int pageIndicatorHeightPx;
115
Winson Chung6e1c0d32013-10-25 15:24:24 -0700116 private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
117
Winson Chung5f8afe62013-08-12 16:19:28 -0700118 DeviceProfile(String n, float w, float h, float r, float c,
119 float is, float its, float hs, float his) {
Winson Chungc58497e2013-09-03 17:48:37 -0700120 // Ensure that we have an odd number of hotseat items (since we need to place all apps)
121 if (!AppsCustomizePagedView.DISABLE_ALL_APPS && hs % 2 == 0) {
122 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
123 }
124
Winson Chung5f8afe62013-08-12 16:19:28 -0700125 name = n;
126 minWidthDps = w;
127 minHeightDps = h;
128 numRows = r;
129 numColumns = c;
130 iconSize = is;
131 iconTextSize = its;
132 numHotseatIcons = hs;
133 hotseatIconSize = his;
134 }
135
Winson Chungf7d45852013-10-10 18:57:15 -0700136 DeviceProfile(Context context,
137 ArrayList<DeviceProfile> profiles,
Winson Chung892c74d2013-08-22 16:15:50 -0700138 float minWidth, float minHeight,
Winson Chung5f8afe62013-08-12 16:19:28 -0700139 int wPx, int hPx,
Winson Chung892c74d2013-08-22 16:15:50 -0700140 int awPx, int ahPx,
Adam Cohen3b185e22013-10-29 14:45:58 -0700141 Resources res) {
142 DisplayMetrics dm = res.getDisplayMetrics();
Winson Chung5f8afe62013-08-12 16:19:28 -0700143 ArrayList<DeviceProfileQuery> points =
144 new ArrayList<DeviceProfileQuery>();
145 transposeLayoutWithOrientation =
Adam Cohen3b185e22013-10-29 14:45:58 -0700146 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700147 minWidthDps = minWidth;
148 minHeightDps = minHeight;
149
Winson Chungf7d45852013-10-10 18:57:15 -0700150 ComponentName cn = new ComponentName(context.getPackageName(),
151 this.getClass().getName());
152 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
Adam Cohen3b185e22013-10-29 14:45:58 -0700153 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
Winson Chungf7d45852013-10-10 18:57:15 -0700154 desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
Adam Cohen3b185e22013-10-29 14:45:58 -0700155 pageIndicatorHeightPx =
156 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
157 defaultPageSpacingPx =
158 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
Winson Chung67ca7e42013-10-31 16:53:19 -0700159 allAppsCellPaddingPx =
160 res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
Winson Chung5f8afe62013-08-12 16:19:28 -0700161
162 // Interpolate the rows
163 for (DeviceProfile p : profiles) {
164 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
165 }
166 numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
167 // Interpolate the columns
168 points.clear();
169 for (DeviceProfile p : profiles) {
170 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
171 }
172 numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
Winson Chung6e1c0d32013-10-25 15:24:24 -0700173 // Interpolate the hotseat length
174 points.clear();
175 for (DeviceProfile p : profiles) {
176 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
177 }
178 numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
179 hotseatAllAppsRank = (int) (numHotseatIcons / 2);
180
Winson Chung5f8afe62013-08-12 16:19:28 -0700181 // Interpolate the icon size
182 points.clear();
183 for (DeviceProfile p : profiles) {
184 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
185 }
186 iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
Winson Chung67ca7e42013-10-31 16:53:19 -0700187 // AllApps uses the original non-scaled icon size
188 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
Winson Chung892c74d2013-08-22 16:15:50 -0700189
Winson Chung5f8afe62013-08-12 16:19:28 -0700190 // Interpolate the icon text size
191 points.clear();
192 for (DeviceProfile p : profiles) {
193 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
194 }
195 iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
Adam Cohen3b185e22013-10-29 14:45:58 -0700196 iconDrawablePaddingOriginalPx =
197 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
Winson Chung67ca7e42013-10-31 16:53:19 -0700198 // AllApps uses the original non-scaled icon text size
199 allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
Winson Chung892c74d2013-08-22 16:15:50 -0700200
Winson Chung5f8afe62013-08-12 16:19:28 -0700201 // Interpolate the hotseat icon size
202 points.clear();
203 for (DeviceProfile p : profiles) {
204 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
205 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700206 // Hotseat
207 hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
Winson Chung892c74d2013-08-22 16:15:50 -0700208
Winson Chung6e1c0d32013-10-25 15:24:24 -0700209 // Calculate the remaining vars
Adam Cohen3b185e22013-10-29 14:45:58 -0700210 updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
Winson Chung6e1c0d32013-10-25 15:24:24 -0700211 updateAvailableDimensions(context);
212 }
213
214 void addCallback(DeviceProfileCallbacks cb) {
215 mCallbacks.add(cb);
216 cb.onAvailableSizeChanged(this);
217 }
218 void removeCallback(DeviceProfileCallbacks cb) {
219 mCallbacks.remove(cb);
220 }
221
222 private int getDeviceOrientation(Context context) {
223 WindowManager windowManager = (WindowManager)
224 context.getSystemService(Context.WINDOW_SERVICE);
225 Resources resources = context.getResources();
226 DisplayMetrics dm = resources.getDisplayMetrics();
227 Configuration config = resources.getConfiguration();
228 int rotation = windowManager.getDefaultDisplay().getRotation();
229
230 boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
231 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
232 boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
233 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
234 if (isLandscape || isRotatedPortrait) {
235 return CellLayout.LANDSCAPE;
236 } else {
237 return CellLayout.PORTRAIT;
238 }
239 }
240
241 private void updateAvailableDimensions(Context context) {
242 WindowManager windowManager = (WindowManager)
243 context.getSystemService(Context.WINDOW_SERVICE);
244 Display display = windowManager.getDefaultDisplay();
245 Resources resources = context.getResources();
246 DisplayMetrics dm = resources.getDisplayMetrics();
247 Configuration config = resources.getConfiguration();
248
249 // There are three possible configurations that the dynamic grid accounts for, portrait,
250 // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
251 // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
252 // the height is the smallest height (either with the nav bar at the bottom or to the
253 // side) and otherwise, the height is simply the largest possible height for a portrait
254 // device.
255 Point size = new Point();
256 Point smallestSize = new Point();
257 Point largestSize = new Point();
258 display.getSize(size);
259 display.getCurrentSizeRange(smallestSize, largestSize);
260 availableWidthPx = size.x;
261 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
262 availableHeightPx = smallestSize.y;
263 } else {
264 availableHeightPx = largestSize.y;
265 }
266
267 // Check to see if the icons fit in the new available height. If not, then we need to
268 // shrink the icon size.
269 Rect workspacePadding = getWorkspacePadding();
270 float scale = 1f;
271 int drawablePadding = iconDrawablePaddingOriginalPx;
272 updateIconSize(1f, drawablePadding, resources, dm);
273 float usedHeight = (cellHeightPx * numRows);
274 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
275 if (usedHeight > maxHeight) {
276 scale = maxHeight / usedHeight;
277 drawablePadding = 0;
278 }
279 updateIconSize(scale, drawablePadding, resources, dm);
280
281 // Make the callbacks
282 for (DeviceProfileCallbacks cb : mCallbacks) {
283 cb.onAvailableSizeChanged(this);
284 }
285 }
286
287 private void updateIconSize(float scale, int drawablePadding, Resources resources,
288 DisplayMetrics dm) {
289 iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
290 iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
291 iconDrawablePaddingPx = drawablePadding;
292 hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
Winson Chung5f8afe62013-08-12 16:19:28 -0700293
294 // Search Bar
295 searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
296 searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
297 searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
298 searchBarSpaceHeightPx = searchBarHeightPx + 2 * edgeMarginPx;
299
300 // Calculate the actual text height
301 Paint textPaint = new Paint();
302 textPaint.setTextSize(iconTextSizePx);
303 FontMetrics fm = textPaint.getFontMetrics();
304 cellWidthPx = iconSizePx;
Winson Chung6e1c0d32013-10-25 15:24:24 -0700305 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
Winson Chung892c74d2013-08-22 16:15:50 -0700306
307 // Hotseat
308 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
309 hotseatCellWidthPx = iconSizePx;
310 hotseatCellHeightPx = iconSizePx;
311
Winson Chung5f8afe62013-08-12 16:19:28 -0700312 // Folder
313 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
Winson Chung6e1c0d32013-10-25 15:24:24 -0700314 folderCellHeightPx = cellHeightPx + edgeMarginPx;
Winson Chung5f8afe62013-08-12 16:19:28 -0700315 folderBackgroundOffset = -edgeMarginPx;
316 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
Winson Chung6e1c0d32013-10-25 15:24:24 -0700317
318 // All Apps
319 Rect padding = getWorkspacePadding(isLandscape ?
320 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
321 int pageIndicatorOffset =
322 resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
Winson Chung67ca7e42013-10-31 16:53:19 -0700323 allAppsCellWidthPx = allAppsIconSizePx;
324 allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
325 int maxLongEdgeCellCount =
326 resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
327 int maxShortEdgeCellCount =
328 resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
329 int minEdgeCellCount =
330 resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
331 int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
332 int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
333
334 allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
335 (allAppsCellHeightPx + allAppsCellPaddingPx);
336 allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
337 allAppsNumCols = (availableWidthPx) /
338 (allAppsCellWidthPx + allAppsCellPaddingPx);
339 allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
Winson Chung5f8afe62013-08-12 16:19:28 -0700340 }
341
Winson Chung6e1c0d32013-10-25 15:24:24 -0700342 void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
Winson Chung892c74d2013-08-22 16:15:50 -0700343 int awPx, int ahPx) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700344 isLandscape = (resources.getConfiguration().orientation ==
345 Configuration.ORIENTATION_LANDSCAPE);
346 isTablet = resources.getBoolean(R.bool.is_tablet);
347 isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
348 widthPx = wPx;
349 heightPx = hPx;
Winson Chung892c74d2013-08-22 16:15:50 -0700350 availableWidthPx = awPx;
351 availableHeightPx = ahPx;
Winson Chungc58497e2013-09-03 17:48:37 -0700352
Winson Chung6e1c0d32013-10-25 15:24:24 -0700353 updateAvailableDimensions(context);
Winson Chung5f8afe62013-08-12 16:19:28 -0700354 }
355
356 private float dist(PointF p0, PointF p1) {
357 return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
358 (p1.y-p0.y)*(p1.y-p0.y));
359 }
360
361 private float weight(PointF a, PointF b,
362 float pow) {
363 float d = dist(a, b);
364 if (d == 0f) {
365 return Float.POSITIVE_INFINITY;
366 }
367 return (float) (1f / Math.pow(d, pow));
368 }
369
370 private float invDistWeightedInterpolate(float width, float height,
371 ArrayList<DeviceProfileQuery> points) {
372 float sum = 0;
373 float weights = 0;
374 float pow = 5;
375 float kNearestNeighbors = 3;
376 final PointF xy = new PointF(width, height);
377
378 ArrayList<DeviceProfileQuery> pointsByNearness = points;
379 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
380 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
381 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
382 }
383 });
384
385 for (int i = 0; i < pointsByNearness.size(); ++i) {
386 DeviceProfileQuery p = pointsByNearness.get(i);
387 if (i < kNearestNeighbors) {
388 float w = weight(xy, p.dimens, pow);
389 if (w == Float.POSITIVE_INFINITY) {
390 return p.value;
391 }
392 weights += w;
393 }
394 }
395
396 for (int i = 0; i < pointsByNearness.size(); ++i) {
397 DeviceProfileQuery p = pointsByNearness.get(i);
398 if (i < kNearestNeighbors) {
399 float w = weight(xy, p.dimens, pow);
400 sum += w * p.value / weights;
401 }
402 }
403
404 return sum;
405 }
406
Winson Chung6e1c0d32013-10-25 15:24:24 -0700407 Rect getWorkspacePadding() {
408 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
409 }
Adam Cohen3b185e22013-10-29 14:45:58 -0700410
Winson Chung5f8afe62013-08-12 16:19:28 -0700411 Rect getWorkspacePadding(int orientation) {
412 Rect padding = new Rect();
Winson Chungfe411c82013-09-26 16:07:17 -0700413 if (orientation == CellLayout.LANDSCAPE &&
414 transposeLayoutWithOrientation) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700415 // Pad the left and right of the workspace with search/hotseat bar sizes
416 padding.set(searchBarSpaceHeightPx, edgeMarginPx,
417 hotseatBarHeightPx, edgeMarginPx);
418 } else {
419 if (isTablet()) {
420 // Pad the left and right of the workspace to ensure consistent spacing
421 // between all icons
422 int width = (orientation == CellLayout.LANDSCAPE)
423 ? Math.max(widthPx, heightPx)
424 : Math.min(widthPx, heightPx);
425 // XXX: If the icon size changes across orientations, we will have to take
426 // that into account here too.
427 int gap = (int) ((width - 2 * edgeMarginPx -
428 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
429 padding.set(edgeMarginPx + gap,
430 searchBarSpaceHeightPx,
431 edgeMarginPx + gap,
432 hotseatBarHeightPx + pageIndicatorHeightPx);
433 } else {
434 // Pad the top and bottom of the workspace with search/hotseat bar sizes
Winson Chungf7d45852013-10-10 18:57:15 -0700435 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
Winson Chung5f8afe62013-08-12 16:19:28 -0700436 searchBarSpaceHeightPx,
Winson Chungf7d45852013-10-10 18:57:15 -0700437 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
Winson Chung5f8afe62013-08-12 16:19:28 -0700438 hotseatBarHeightPx + pageIndicatorHeightPx);
439 }
440 }
441 return padding;
442 }
443
Adam Cohen3b185e22013-10-29 14:45:58 -0700444 int getWorkspacePageSpacing(int orientation) {
445 if (orientation == CellLayout.LANDSCAPE &&
446 transposeLayoutWithOrientation) {
447 // In landscape mode the page spacing is set to the default.
448 return defaultPageSpacingPx;
449 } else {
450 // In portrait, we want the pages spaced such that there is no
451 // overhang of the previous / next page into the current page viewport.
452 // We assume symmetrical padding in portrait mode.
453 return getWorkspacePadding().left;
454 }
455 }
456
Winson Chungabedd9f2013-09-24 15:41:09 -0700457 // The rect returned will be extended to below the system ui that covers the workspace
458 Rect getHotseatRect() {
459 if (isVerticalBarLayout()) {
460 return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
461 Integer.MAX_VALUE, availableHeightPx);
462 } else {
463 return new Rect(0, availableHeightPx - hotseatBarHeightPx,
464 availableWidthPx, Integer.MAX_VALUE);
465 }
466 }
467
Winson Chung5f8afe62013-08-12 16:19:28 -0700468 int calculateCellWidth(int width, int countX) {
469 return width / countX;
470 }
471 int calculateCellHeight(int height, int countY) {
472 return height / countY;
473 }
474
Winson Chungaf40f202013-09-18 18:26:31 -0700475 boolean isPhone() {
476 return !isTablet && !isLargeTablet;
477 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700478 boolean isTablet() {
479 return isTablet;
480 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700481 boolean isLargeTablet() {
482 return isLargeTablet;
483 }
484
Winson Chungabedd9f2013-09-24 15:41:09 -0700485 boolean isVerticalBarLayout() {
486 return isLandscape && transposeLayoutWithOrientation;
487 }
488
Adam Cohen3b185e22013-10-29 14:45:58 -0700489 boolean shouldFadeAdjacentWorkspaceScreens() {
490 return isVerticalBarLayout() || isLargeTablet();
491 }
492
Winson Chung5f8afe62013-08-12 16:19:28 -0700493 public void layout(Launcher launcher) {
494 FrameLayout.LayoutParams lp;
495 Resources res = launcher.getResources();
Winson Chungabedd9f2013-09-24 15:41:09 -0700496 boolean hasVerticalBarLayout = isVerticalBarLayout();
Winson Chung5f8afe62013-08-12 16:19:28 -0700497
498 // Layout the search bar space
Winson Chung54e65132013-09-27 11:44:58 -0700499 View searchBar = launcher.getSearchBar();
500 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
Winson Chung5f8afe62013-08-12 16:19:28 -0700501 if (hasVerticalBarLayout) {
502 // Vertical search bar
503 lp.gravity = Gravity.TOP | Gravity.LEFT;
504 lp.width = searchBarSpaceHeightPx;
505 lp.height = LayoutParams.MATCH_PARENT;
Winson Chung54e65132013-09-27 11:44:58 -0700506 searchBar.setPadding(
Winson Chung5f8afe62013-08-12 16:19:28 -0700507 0, 2 * edgeMarginPx, 0,
508 2 * edgeMarginPx);
509 } else {
510 // Horizontal search bar
511 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
512 lp.width = searchBarSpaceWidthPx;
513 lp.height = searchBarSpaceHeightPx;
Winson Chung54e65132013-09-27 11:44:58 -0700514 searchBar.setPadding(
Winson Chung5f8afe62013-08-12 16:19:28 -0700515 2 * edgeMarginPx,
516 2 * edgeMarginPx,
517 2 * edgeMarginPx, 0);
518 }
Winson Chung54e65132013-09-27 11:44:58 -0700519 searchBar.setLayoutParams(lp);
Winson Chung5f8afe62013-08-12 16:19:28 -0700520
521 // Layout the search bar
Winson Chung54e65132013-09-27 11:44:58 -0700522 View qsbBar = launcher.getQsbBar();
523 LayoutParams vglp = qsbBar.getLayoutParams();
524 vglp.width = LayoutParams.MATCH_PARENT;
525 vglp.height = LayoutParams.MATCH_PARENT;
526 qsbBar.setLayoutParams(vglp);
Winson Chung5f8afe62013-08-12 16:19:28 -0700527
528 // Layout the voice proxy
529 View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
530 if (voiceButtonProxy != null) {
531 if (hasVerticalBarLayout) {
532 // TODO: MOVE THIS INTO SEARCH BAR MEASURE
533 } else {
534 lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
535 lp.gravity = Gravity.TOP | Gravity.END;
536 lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
537 2 * iconSizePx;
538 lp.height = searchBarSpaceHeightPx;
539 }
540 }
541
542 // Layout the workspace
Adam Cohen3b185e22013-10-29 14:45:58 -0700543 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
Winson Chung5f8afe62013-08-12 16:19:28 -0700544 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
545 lp.gravity = Gravity.CENTER;
Adam Cohen3b185e22013-10-29 14:45:58 -0700546 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
547 Rect padding = getWorkspacePadding(orientation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700548 workspace.setLayoutParams(lp);
Adam Cohen3b185e22013-10-29 14:45:58 -0700549 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
550 workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
Winson Chung5f8afe62013-08-12 16:19:28 -0700551
552 // Layout the hotseat
553 View hotseat = launcher.findViewById(R.id.hotseat);
554 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
555 if (hasVerticalBarLayout) {
556 // Vertical hotseat
557 lp.gravity = Gravity.RIGHT;
558 lp.width = hotseatBarHeightPx;
559 lp.height = LayoutParams.MATCH_PARENT;
Winson Chung6e1c0d32013-10-25 15:24:24 -0700560 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
Winson Chung5f8afe62013-08-12 16:19:28 -0700561 } else if (isTablet()) {
562 // Pad the hotseat with the grid gap calculated above
563 int gridGap = (int) ((widthPx - 2 * edgeMarginPx -
564 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
565 int gridWidth = (int) ((numColumns * cellWidthPx) +
566 ((numColumns - 1) * gridGap));
567 int hotseatGap = (int) Math.max(0,
568 (gridWidth - (numHotseatIcons * hotseatCellWidthPx))
569 / (numHotseatIcons - 1));
570 lp.gravity = Gravity.BOTTOM;
571 lp.width = LayoutParams.MATCH_PARENT;
572 lp.height = hotseatBarHeightPx;
573 hotseat.setPadding(2 * edgeMarginPx + gridGap + hotseatGap, 0,
574 2 * edgeMarginPx + gridGap + hotseatGap,
575 2 * edgeMarginPx);
576 } else {
577 // For phones, layout the hotseat without any bottom margin
578 // to ensure that we have space for the folders
579 lp.gravity = Gravity.BOTTOM;
580 lp.width = LayoutParams.MATCH_PARENT;
581 lp.height = hotseatBarHeightPx;
Winson Chung2d75f122013-09-23 16:53:31 -0700582 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
Winson Chung5f8afe62013-08-12 16:19:28 -0700583 2 * edgeMarginPx, 0);
584 }
585 hotseat.setLayoutParams(lp);
586
587 // Layout the page indicators
588 View pageIndicator = launcher.findViewById(R.id.page_indicator);
589 if (pageIndicator != null) {
590 if (hasVerticalBarLayout) {
591 // Hide the page indicators when we have vertical search/hotseat
592 pageIndicator.setVisibility(View.GONE);
593 } else {
594 // Put the page indicators above the hotseat
595 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
596 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
597 lp.width = LayoutParams.WRAP_CONTENT;
Winson Chungf4bd2362013-10-07 17:11:27 -0700598 lp.height = LayoutParams.WRAP_CONTENT;
Winson Chung5f8afe62013-08-12 16:19:28 -0700599 lp.bottomMargin = hotseatBarHeightPx;
600 pageIndicator.setLayoutParams(lp);
601 }
602 }
Winson Chung67ca7e42013-10-31 16:53:19 -0700603
604 AppsCustomizeTabHost host = (AppsCustomizeTabHost)
605 launcher.findViewById(R.id.apps_customize_pane);
606 if (host != null) {
607 // Center the all apps page indicator
608 int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
609 (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
610 pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
611 if (pageIndicator != null) {
612 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
613 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
614 lp.width = LayoutParams.WRAP_CONTENT;
615 lp.height = pageIndicatorHeight;
616 pageIndicator.setLayoutParams(lp);
617 }
618
619 AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
620 host.findViewById(R.id.apps_customize_pane_content);
621 padding = new Rect();
622 if (pagedView != null) {
623 // Constrain the dimensions of all apps so that it does not span the full width
624 int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
625 (2 * (allAppsNumCols + 1));
626 int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
627 (2 * (allAppsNumRows + 1));
628 paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
629 paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
630 int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
631 int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
632 if (gridPaddingLR > (allAppsCellWidthPx / 4)) {
633 padding.left = padding.right = gridPaddingLR;
634 }
635 // The icons are centered, so we can't just offset by the page indicator height
636 // because the empty space will actually be pageIndicatorHeight + paddingTB
637 padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
638 pagedView.setAllAppsPadding(padding);
639 pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
640 }
641 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700642 }
643}
644
645public class DynamicGrid {
646 @SuppressWarnings("unused")
647 private static final String TAG = "DynamicGrid";
648
649 private DeviceProfile mProfile;
650 private float mMinWidth;
651 private float mMinHeight;
652
Winson Chung67ca7e42013-10-31 16:53:19 -0700653 // This is a static that we use for the default icon size on a 4/5-inch phone
654 static float DEFAULT_ICON_SIZE_DP = 60;
655 static float DEFAULT_ICON_SIZE_PX = 0;
656
Winson Chung892c74d2013-08-22 16:15:50 -0700657 public static float dpiFromPx(int size, DisplayMetrics metrics){
Winson Chung5f8afe62013-08-12 16:19:28 -0700658 float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
Winson Chung892c74d2013-08-22 16:15:50 -0700659 return (size / densityRatio);
660 }
661 public static int pxFromDp(float size, DisplayMetrics metrics) {
662 return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
663 size, metrics));
664 }
665 public static int pxFromSp(float size, DisplayMetrics metrics) {
666 return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
667 size, metrics));
Winson Chung5f8afe62013-08-12 16:19:28 -0700668 }
669
Winson Chungf7d45852013-10-10 18:57:15 -0700670 public DynamicGrid(Context context, Resources resources,
671 int minWidthPx, int minHeightPx,
Winson Chung892c74d2013-08-22 16:15:50 -0700672 int widthPx, int heightPx,
673 int awPx, int ahPx) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700674 DisplayMetrics dm = resources.getDisplayMetrics();
675 ArrayList<DeviceProfile> deviceProfiles =
676 new ArrayList<DeviceProfile>();
Winson Chungc58497e2013-09-03 17:48:37 -0700677 boolean hasAA = !AppsCustomizePagedView.DISABLE_ALL_APPS;
Winson Chung67ca7e42013-10-31 16:53:19 -0700678 DEFAULT_ICON_SIZE_PX = pxFromDp(DEFAULT_ICON_SIZE_DP, dm);
Winson Chung5f8afe62013-08-12 16:19:28 -0700679 // Our phone profiles include the bar sizes in each orientation
680 deviceProfiles.add(new DeviceProfile("Super Short Stubby",
Winson Chung2651d132013-10-01 16:28:15 -0700681 255, 300, 2, 3, 48, 13, (hasAA ? 5 : 4), 48));
Winson Chung5f8afe62013-08-12 16:19:28 -0700682 deviceProfiles.add(new DeviceProfile("Shorter Stubby",
Winson Chung2651d132013-10-01 16:28:15 -0700683 255, 400, 3, 3, 48, 13, (hasAA ? 5 : 4), 48));
Winson Chung5f8afe62013-08-12 16:19:28 -0700684 deviceProfiles.add(new DeviceProfile("Short Stubby",
Winson Chung2651d132013-10-01 16:28:15 -0700685 275, 420, 3, 4, 48, 13, (hasAA ? 5 : 4), 48));
Winson Chung5f8afe62013-08-12 16:19:28 -0700686 deviceProfiles.add(new DeviceProfile("Stubby",
Winson Chung2651d132013-10-01 16:28:15 -0700687 255, 450, 3, 4, 48, 13, (hasAA ? 5 : 4), 48));
Winson Chung5f8afe62013-08-12 16:19:28 -0700688 deviceProfiles.add(new DeviceProfile("Nexus S",
Winson Chung2651d132013-10-01 16:28:15 -0700689 296, 491.33f, 4, 4, 48, 13, (hasAA ? 5 : 4), 48));
Winson Chung5f8afe62013-08-12 16:19:28 -0700690 deviceProfiles.add(new DeviceProfile("Nexus 4",
Winson Chung67ca7e42013-10-31 16:53:19 -0700691 359, 518, 4, 4, DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 4), 56));
Winson Chung5f8afe62013-08-12 16:19:28 -0700692 // The tablet profile is odd in that the landscape orientation
693 // also includes the nav bar on the side
694 deviceProfiles.add(new DeviceProfile("Nexus 7",
695 575, 904, 6, 6, 72, 14.4f, 7, 60));
696 // Larger tablet profiles always have system bars on the top & bottom
697 deviceProfiles.add(new DeviceProfile("Nexus 10",
698 727, 1207, 5, 8, 80, 14.4f, 9, 64));
699 /*
700 deviceProfiles.add(new DeviceProfile("Nexus 7",
701 600, 960, 5, 5, 72, 14.4f, 5, 60));
702 deviceProfiles.add(new DeviceProfile("Nexus 10",
Winson Chungc58497e2013-09-03 17:48:37 -0700703 800, 1280, 5, 5, 80, 14.4f, (hasAA ? 7 : 6), 64));
Winson Chung5f8afe62013-08-12 16:19:28 -0700704 */
705 deviceProfiles.add(new DeviceProfile("20-inch Tablet",
706 1527, 2527, 7, 7, 100, 20, 7, 72));
707 mMinWidth = dpiFromPx(minWidthPx, dm);
708 mMinHeight = dpiFromPx(minHeightPx, dm);
Winson Chungf7d45852013-10-10 18:57:15 -0700709 mProfile = new DeviceProfile(context, deviceProfiles,
Winson Chung892c74d2013-08-22 16:15:50 -0700710 mMinWidth, mMinHeight,
Winson Chung5f8afe62013-08-12 16:19:28 -0700711 widthPx, heightPx,
Winson Chung892c74d2013-08-22 16:15:50 -0700712 awPx, ahPx,
Winson Chung5f8afe62013-08-12 16:19:28 -0700713 resources);
714 }
715
716 DeviceProfile getDeviceProfile() {
717 return mProfile;
718 }
719
720 public String toString() {
721 return "-------- DYNAMIC GRID ------- \n" +
722 "Wd: " + mProfile.minWidthDps + ", Hd: " + mProfile.minHeightDps +
723 ", W: " + mProfile.widthPx + ", H: " + mProfile.heightPx +
724 " [r: " + mProfile.numRows + ", c: " + mProfile.numColumns +
Winson Chung6e1c0d32013-10-25 15:24:24 -0700725 ", is: " + mProfile.iconSizePx + ", its: " + mProfile.iconTextSizePx +
Winson Chung5f8afe62013-08-12 16:19:28 -0700726 ", cw: " + mProfile.cellWidthPx + ", ch: " + mProfile.cellHeightPx +
727 ", hc: " + mProfile.numHotseatIcons + ", his: " + mProfile.hotseatIconSizePx + "]";
728 }
729}