blob: ccbac2ce21471c52a2cadec35d0164486118d302 [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;
94 int folderBackgroundOffset;
95 int folderIconSizePx;
96 int folderCellWidthPx;
97 int folderCellHeightPx;
98 int hotseatCellWidthPx;
99 int hotseatCellHeightPx;
100 int hotseatIconSizePx;
101 int hotseatBarHeightPx;
Winson Chungc58497e2013-09-03 17:48:37 -0700102 int hotseatAllAppsRank;
103 int allAppsNumRows;
104 int allAppsNumCols;
Winson Chung5f8afe62013-08-12 16:19:28 -0700105 int searchBarSpaceWidthPx;
106 int searchBarSpaceMaxWidthPx;
107 int searchBarSpaceHeightPx;
108 int searchBarHeightPx;
109 int pageIndicatorHeightPx;
110
Winson Chung6e1c0d32013-10-25 15:24:24 -0700111 private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
112
Winson Chung5f8afe62013-08-12 16:19:28 -0700113 DeviceProfile(String n, float w, float h, float r, float c,
114 float is, float its, float hs, float his) {
Winson Chungc58497e2013-09-03 17:48:37 -0700115 // Ensure that we have an odd number of hotseat items (since we need to place all apps)
116 if (!AppsCustomizePagedView.DISABLE_ALL_APPS && hs % 2 == 0) {
117 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
118 }
119
Winson Chung5f8afe62013-08-12 16:19:28 -0700120 name = n;
121 minWidthDps = w;
122 minHeightDps = h;
123 numRows = r;
124 numColumns = c;
125 iconSize = is;
126 iconTextSize = its;
127 numHotseatIcons = hs;
128 hotseatIconSize = his;
129 }
130
Winson Chungf7d45852013-10-10 18:57:15 -0700131 DeviceProfile(Context context,
132 ArrayList<DeviceProfile> profiles,
Winson Chung892c74d2013-08-22 16:15:50 -0700133 float minWidth, float minHeight,
Winson Chung5f8afe62013-08-12 16:19:28 -0700134 int wPx, int hPx,
Winson Chung892c74d2013-08-22 16:15:50 -0700135 int awPx, int ahPx,
Adam Cohen3b185e22013-10-29 14:45:58 -0700136 Resources res) {
137 DisplayMetrics dm = res.getDisplayMetrics();
Winson Chung5f8afe62013-08-12 16:19:28 -0700138 ArrayList<DeviceProfileQuery> points =
139 new ArrayList<DeviceProfileQuery>();
140 transposeLayoutWithOrientation =
Adam Cohen3b185e22013-10-29 14:45:58 -0700141 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700142 minWidthDps = minWidth;
143 minHeightDps = minHeight;
144
Winson Chungf7d45852013-10-10 18:57:15 -0700145 ComponentName cn = new ComponentName(context.getPackageName(),
146 this.getClass().getName());
147 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
Adam Cohen3b185e22013-10-29 14:45:58 -0700148 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
Winson Chungf7d45852013-10-10 18:57:15 -0700149 desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
Adam Cohen3b185e22013-10-29 14:45:58 -0700150 pageIndicatorHeightPx =
151 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
152 defaultPageSpacingPx =
153 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
Winson Chung5f8afe62013-08-12 16:19:28 -0700154
155 // Interpolate the rows
156 for (DeviceProfile p : profiles) {
157 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
158 }
159 numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
160 // Interpolate the columns
161 points.clear();
162 for (DeviceProfile p : profiles) {
163 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
164 }
165 numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
Winson Chung6e1c0d32013-10-25 15:24:24 -0700166 // Interpolate the hotseat length
167 points.clear();
168 for (DeviceProfile p : profiles) {
169 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
170 }
171 numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
172 hotseatAllAppsRank = (int) (numHotseatIcons / 2);
173
Winson Chung5f8afe62013-08-12 16:19:28 -0700174 // Interpolate the icon size
175 points.clear();
176 for (DeviceProfile p : profiles) {
177 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
178 }
179 iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
Winson Chung892c74d2013-08-22 16:15:50 -0700180
Winson Chung5f8afe62013-08-12 16:19:28 -0700181 // Interpolate the icon text size
182 points.clear();
183 for (DeviceProfile p : profiles) {
184 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
185 }
186 iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
Adam Cohen3b185e22013-10-29 14:45:58 -0700187 iconDrawablePaddingOriginalPx =
188 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
Winson Chung892c74d2013-08-22 16:15:50 -0700189
Winson Chung5f8afe62013-08-12 16:19:28 -0700190 // Interpolate the hotseat icon size
191 points.clear();
192 for (DeviceProfile p : profiles) {
193 points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
194 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700195 // Hotseat
196 hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
Winson Chung892c74d2013-08-22 16:15:50 -0700197
Winson Chung6e1c0d32013-10-25 15:24:24 -0700198 // Calculate the remaining vars
Adam Cohen3b185e22013-10-29 14:45:58 -0700199 updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
Winson Chung6e1c0d32013-10-25 15:24:24 -0700200 updateAvailableDimensions(context);
201 }
202
203 void addCallback(DeviceProfileCallbacks cb) {
204 mCallbacks.add(cb);
205 cb.onAvailableSizeChanged(this);
206 }
207 void removeCallback(DeviceProfileCallbacks cb) {
208 mCallbacks.remove(cb);
209 }
210
211 private int getDeviceOrientation(Context context) {
212 WindowManager windowManager = (WindowManager)
213 context.getSystemService(Context.WINDOW_SERVICE);
214 Resources resources = context.getResources();
215 DisplayMetrics dm = resources.getDisplayMetrics();
216 Configuration config = resources.getConfiguration();
217 int rotation = windowManager.getDefaultDisplay().getRotation();
218
219 boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
220 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
221 boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
222 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
223 if (isLandscape || isRotatedPortrait) {
224 return CellLayout.LANDSCAPE;
225 } else {
226 return CellLayout.PORTRAIT;
227 }
228 }
229
230 private void updateAvailableDimensions(Context context) {
231 WindowManager windowManager = (WindowManager)
232 context.getSystemService(Context.WINDOW_SERVICE);
233 Display display = windowManager.getDefaultDisplay();
234 Resources resources = context.getResources();
235 DisplayMetrics dm = resources.getDisplayMetrics();
236 Configuration config = resources.getConfiguration();
237
238 // There are three possible configurations that the dynamic grid accounts for, portrait,
239 // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
240 // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
241 // the height is the smallest height (either with the nav bar at the bottom or to the
242 // side) and otherwise, the height is simply the largest possible height for a portrait
243 // device.
244 Point size = new Point();
245 Point smallestSize = new Point();
246 Point largestSize = new Point();
247 display.getSize(size);
248 display.getCurrentSizeRange(smallestSize, largestSize);
249 availableWidthPx = size.x;
250 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
251 availableHeightPx = smallestSize.y;
252 } else {
253 availableHeightPx = largestSize.y;
254 }
255
256 // Check to see if the icons fit in the new available height. If not, then we need to
257 // shrink the icon size.
258 Rect workspacePadding = getWorkspacePadding();
259 float scale = 1f;
260 int drawablePadding = iconDrawablePaddingOriginalPx;
261 updateIconSize(1f, drawablePadding, resources, dm);
262 float usedHeight = (cellHeightPx * numRows);
263 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
264 if (usedHeight > maxHeight) {
265 scale = maxHeight / usedHeight;
266 drawablePadding = 0;
267 }
268 updateIconSize(scale, drawablePadding, resources, dm);
269
270 // Make the callbacks
271 for (DeviceProfileCallbacks cb : mCallbacks) {
272 cb.onAvailableSizeChanged(this);
273 }
274 }
275
276 private void updateIconSize(float scale, int drawablePadding, Resources resources,
277 DisplayMetrics dm) {
278 iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
279 iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
280 iconDrawablePaddingPx = drawablePadding;
281 hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
Winson Chung5f8afe62013-08-12 16:19:28 -0700282
283 // Search Bar
284 searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
285 searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
286 searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
287 searchBarSpaceHeightPx = searchBarHeightPx + 2 * edgeMarginPx;
288
289 // Calculate the actual text height
290 Paint textPaint = new Paint();
291 textPaint.setTextSize(iconTextSizePx);
292 FontMetrics fm = textPaint.getFontMetrics();
293 cellWidthPx = iconSizePx;
Winson Chung6e1c0d32013-10-25 15:24:24 -0700294 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
Winson Chung892c74d2013-08-22 16:15:50 -0700295
296 // Hotseat
297 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
298 hotseatCellWidthPx = iconSizePx;
299 hotseatCellHeightPx = iconSizePx;
300
Winson Chung5f8afe62013-08-12 16:19:28 -0700301 // Folder
302 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
Winson Chung6e1c0d32013-10-25 15:24:24 -0700303 folderCellHeightPx = cellHeightPx + edgeMarginPx;
Winson Chung5f8afe62013-08-12 16:19:28 -0700304 folderBackgroundOffset = -edgeMarginPx;
305 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
Winson Chung6e1c0d32013-10-25 15:24:24 -0700306
307 // All Apps
308 Rect padding = getWorkspacePadding(isLandscape ?
309 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
310 int pageIndicatorOffset =
311 resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
312 if (isLandscape) {
313 allAppsNumRows = (availableHeightPx - pageIndicatorOffset - 4 * edgeMarginPx) /
314 (iconSizePx + iconTextSizePx + 2 * edgeMarginPx);
315 } else {
316 allAppsNumRows = (int) numRows + 1;
317 }
318 allAppsNumCols = (availableWidthPx - padding.left - padding.right - 2 * edgeMarginPx) /
319 (iconSizePx + 2 * edgeMarginPx);
Winson Chung5f8afe62013-08-12 16:19:28 -0700320 }
321
Winson Chung6e1c0d32013-10-25 15:24:24 -0700322 void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
Winson Chung892c74d2013-08-22 16:15:50 -0700323 int awPx, int ahPx) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700324 isLandscape = (resources.getConfiguration().orientation ==
325 Configuration.ORIENTATION_LANDSCAPE);
326 isTablet = resources.getBoolean(R.bool.is_tablet);
327 isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
328 widthPx = wPx;
329 heightPx = hPx;
Winson Chung892c74d2013-08-22 16:15:50 -0700330 availableWidthPx = awPx;
331 availableHeightPx = ahPx;
Winson Chungc58497e2013-09-03 17:48:37 -0700332
Winson Chung6e1c0d32013-10-25 15:24:24 -0700333 updateAvailableDimensions(context);
Winson Chung5f8afe62013-08-12 16:19:28 -0700334 }
335
336 private float dist(PointF p0, PointF p1) {
337 return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
338 (p1.y-p0.y)*(p1.y-p0.y));
339 }
340
341 private float weight(PointF a, PointF b,
342 float pow) {
343 float d = dist(a, b);
344 if (d == 0f) {
345 return Float.POSITIVE_INFINITY;
346 }
347 return (float) (1f / Math.pow(d, pow));
348 }
349
350 private float invDistWeightedInterpolate(float width, float height,
351 ArrayList<DeviceProfileQuery> points) {
352 float sum = 0;
353 float weights = 0;
354 float pow = 5;
355 float kNearestNeighbors = 3;
356 final PointF xy = new PointF(width, height);
357
358 ArrayList<DeviceProfileQuery> pointsByNearness = points;
359 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
360 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
361 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
362 }
363 });
364
365 for (int i = 0; i < pointsByNearness.size(); ++i) {
366 DeviceProfileQuery p = pointsByNearness.get(i);
367 if (i < kNearestNeighbors) {
368 float w = weight(xy, p.dimens, pow);
369 if (w == Float.POSITIVE_INFINITY) {
370 return p.value;
371 }
372 weights += w;
373 }
374 }
375
376 for (int i = 0; i < pointsByNearness.size(); ++i) {
377 DeviceProfileQuery p = pointsByNearness.get(i);
378 if (i < kNearestNeighbors) {
379 float w = weight(xy, p.dimens, pow);
380 sum += w * p.value / weights;
381 }
382 }
383
384 return sum;
385 }
386
Winson Chung6e1c0d32013-10-25 15:24:24 -0700387 Rect getWorkspacePadding() {
388 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
389 }
Adam Cohen3b185e22013-10-29 14:45:58 -0700390
Winson Chung5f8afe62013-08-12 16:19:28 -0700391 Rect getWorkspacePadding(int orientation) {
392 Rect padding = new Rect();
Winson Chungfe411c82013-09-26 16:07:17 -0700393 if (orientation == CellLayout.LANDSCAPE &&
394 transposeLayoutWithOrientation) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700395 // Pad the left and right of the workspace with search/hotseat bar sizes
396 padding.set(searchBarSpaceHeightPx, edgeMarginPx,
397 hotseatBarHeightPx, edgeMarginPx);
398 } else {
399 if (isTablet()) {
400 // Pad the left and right of the workspace to ensure consistent spacing
401 // between all icons
402 int width = (orientation == CellLayout.LANDSCAPE)
403 ? Math.max(widthPx, heightPx)
404 : Math.min(widthPx, heightPx);
405 // XXX: If the icon size changes across orientations, we will have to take
406 // that into account here too.
407 int gap = (int) ((width - 2 * edgeMarginPx -
408 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
409 padding.set(edgeMarginPx + gap,
410 searchBarSpaceHeightPx,
411 edgeMarginPx + gap,
412 hotseatBarHeightPx + pageIndicatorHeightPx);
413 } else {
414 // Pad the top and bottom of the workspace with search/hotseat bar sizes
Winson Chungf7d45852013-10-10 18:57:15 -0700415 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
Winson Chung5f8afe62013-08-12 16:19:28 -0700416 searchBarSpaceHeightPx,
Winson Chungf7d45852013-10-10 18:57:15 -0700417 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
Winson Chung5f8afe62013-08-12 16:19:28 -0700418 hotseatBarHeightPx + pageIndicatorHeightPx);
419 }
420 }
421 return padding;
422 }
423
Adam Cohen3b185e22013-10-29 14:45:58 -0700424 int getWorkspacePageSpacing(int orientation) {
425 if (orientation == CellLayout.LANDSCAPE &&
426 transposeLayoutWithOrientation) {
427 // In landscape mode the page spacing is set to the default.
428 return defaultPageSpacingPx;
429 } else {
430 // In portrait, we want the pages spaced such that there is no
431 // overhang of the previous / next page into the current page viewport.
432 // We assume symmetrical padding in portrait mode.
433 return getWorkspacePadding().left;
434 }
435 }
436
Winson Chungabedd9f2013-09-24 15:41:09 -0700437 // The rect returned will be extended to below the system ui that covers the workspace
438 Rect getHotseatRect() {
439 if (isVerticalBarLayout()) {
440 return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
441 Integer.MAX_VALUE, availableHeightPx);
442 } else {
443 return new Rect(0, availableHeightPx - hotseatBarHeightPx,
444 availableWidthPx, Integer.MAX_VALUE);
445 }
446 }
447
Winson Chung5f8afe62013-08-12 16:19:28 -0700448 int calculateCellWidth(int width, int countX) {
449 return width / countX;
450 }
451 int calculateCellHeight(int height, int countY) {
452 return height / countY;
453 }
454
Winson Chungaf40f202013-09-18 18:26:31 -0700455 boolean isPhone() {
456 return !isTablet && !isLargeTablet;
457 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700458 boolean isTablet() {
459 return isTablet;
460 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700461 boolean isLargeTablet() {
462 return isLargeTablet;
463 }
464
Winson Chungabedd9f2013-09-24 15:41:09 -0700465 boolean isVerticalBarLayout() {
466 return isLandscape && transposeLayoutWithOrientation;
467 }
468
Adam Cohen3b185e22013-10-29 14:45:58 -0700469 boolean shouldFadeAdjacentWorkspaceScreens() {
470 return isVerticalBarLayout() || isLargeTablet();
471 }
472
Winson Chung5f8afe62013-08-12 16:19:28 -0700473 public void layout(Launcher launcher) {
474 FrameLayout.LayoutParams lp;
475 Resources res = launcher.getResources();
Winson Chungabedd9f2013-09-24 15:41:09 -0700476 boolean hasVerticalBarLayout = isVerticalBarLayout();
Winson Chung5f8afe62013-08-12 16:19:28 -0700477
478 // Layout the search bar space
Winson Chung54e65132013-09-27 11:44:58 -0700479 View searchBar = launcher.getSearchBar();
480 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
Winson Chung5f8afe62013-08-12 16:19:28 -0700481 if (hasVerticalBarLayout) {
482 // Vertical search bar
483 lp.gravity = Gravity.TOP | Gravity.LEFT;
484 lp.width = searchBarSpaceHeightPx;
485 lp.height = LayoutParams.MATCH_PARENT;
Winson Chung54e65132013-09-27 11:44:58 -0700486 searchBar.setPadding(
Winson Chung5f8afe62013-08-12 16:19:28 -0700487 0, 2 * edgeMarginPx, 0,
488 2 * edgeMarginPx);
489 } else {
490 // Horizontal search bar
491 lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
492 lp.width = searchBarSpaceWidthPx;
493 lp.height = searchBarSpaceHeightPx;
Winson Chung54e65132013-09-27 11:44:58 -0700494 searchBar.setPadding(
Winson Chung5f8afe62013-08-12 16:19:28 -0700495 2 * edgeMarginPx,
496 2 * edgeMarginPx,
497 2 * edgeMarginPx, 0);
498 }
Winson Chung54e65132013-09-27 11:44:58 -0700499 searchBar.setLayoutParams(lp);
Winson Chung5f8afe62013-08-12 16:19:28 -0700500
501 // Layout the search bar
Winson Chung54e65132013-09-27 11:44:58 -0700502 View qsbBar = launcher.getQsbBar();
503 LayoutParams vglp = qsbBar.getLayoutParams();
504 vglp.width = LayoutParams.MATCH_PARENT;
505 vglp.height = LayoutParams.MATCH_PARENT;
506 qsbBar.setLayoutParams(vglp);
Winson Chung5f8afe62013-08-12 16:19:28 -0700507
508 // Layout the voice proxy
509 View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
510 if (voiceButtonProxy != null) {
511 if (hasVerticalBarLayout) {
512 // TODO: MOVE THIS INTO SEARCH BAR MEASURE
513 } else {
514 lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
515 lp.gravity = Gravity.TOP | Gravity.END;
516 lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
517 2 * iconSizePx;
518 lp.height = searchBarSpaceHeightPx;
519 }
520 }
521
522 // Layout the workspace
Adam Cohen3b185e22013-10-29 14:45:58 -0700523 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
Winson Chung5f8afe62013-08-12 16:19:28 -0700524 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
525 lp.gravity = Gravity.CENTER;
Adam Cohen3b185e22013-10-29 14:45:58 -0700526 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
527 Rect padding = getWorkspacePadding(orientation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700528 workspace.setLayoutParams(lp);
Adam Cohen3b185e22013-10-29 14:45:58 -0700529 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
530 workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
Winson Chung5f8afe62013-08-12 16:19:28 -0700531
532 // Layout the hotseat
533 View hotseat = launcher.findViewById(R.id.hotseat);
534 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
535 if (hasVerticalBarLayout) {
536 // Vertical hotseat
537 lp.gravity = Gravity.RIGHT;
538 lp.width = hotseatBarHeightPx;
539 lp.height = LayoutParams.MATCH_PARENT;
Winson Chung6e1c0d32013-10-25 15:24:24 -0700540 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
Winson Chung5f8afe62013-08-12 16:19:28 -0700541 } else if (isTablet()) {
542 // Pad the hotseat with the grid gap calculated above
543 int gridGap = (int) ((widthPx - 2 * edgeMarginPx -
544 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
545 int gridWidth = (int) ((numColumns * cellWidthPx) +
546 ((numColumns - 1) * gridGap));
547 int hotseatGap = (int) Math.max(0,
548 (gridWidth - (numHotseatIcons * hotseatCellWidthPx))
549 / (numHotseatIcons - 1));
550 lp.gravity = Gravity.BOTTOM;
551 lp.width = LayoutParams.MATCH_PARENT;
552 lp.height = hotseatBarHeightPx;
553 hotseat.setPadding(2 * edgeMarginPx + gridGap + hotseatGap, 0,
554 2 * edgeMarginPx + gridGap + hotseatGap,
555 2 * edgeMarginPx);
556 } else {
557 // For phones, layout the hotseat without any bottom margin
558 // to ensure that we have space for the folders
559 lp.gravity = Gravity.BOTTOM;
560 lp.width = LayoutParams.MATCH_PARENT;
561 lp.height = hotseatBarHeightPx;
Winson Chung2d75f122013-09-23 16:53:31 -0700562 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
Winson Chung5f8afe62013-08-12 16:19:28 -0700563 2 * edgeMarginPx, 0);
564 }
565 hotseat.setLayoutParams(lp);
566
567 // Layout the page indicators
568 View pageIndicator = launcher.findViewById(R.id.page_indicator);
569 if (pageIndicator != null) {
570 if (hasVerticalBarLayout) {
571 // Hide the page indicators when we have vertical search/hotseat
572 pageIndicator.setVisibility(View.GONE);
573 } else {
574 // Put the page indicators above the hotseat
575 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
576 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
577 lp.width = LayoutParams.WRAP_CONTENT;
Winson Chungf4bd2362013-10-07 17:11:27 -0700578 lp.height = LayoutParams.WRAP_CONTENT;
Winson Chung5f8afe62013-08-12 16:19:28 -0700579 lp.bottomMargin = hotseatBarHeightPx;
580 pageIndicator.setLayoutParams(lp);
581 }
582 }
583 }
584}
585
586public class DynamicGrid {
587 @SuppressWarnings("unused")
588 private static final String TAG = "DynamicGrid";
589
590 private DeviceProfile mProfile;
591 private float mMinWidth;
592 private float mMinHeight;
593
Winson Chung892c74d2013-08-22 16:15:50 -0700594 public static float dpiFromPx(int size, DisplayMetrics metrics){
Winson Chung5f8afe62013-08-12 16:19:28 -0700595 float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
Winson Chung892c74d2013-08-22 16:15:50 -0700596 return (size / densityRatio);
597 }
598 public static int pxFromDp(float size, DisplayMetrics metrics) {
599 return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
600 size, metrics));
601 }
602 public static int pxFromSp(float size, DisplayMetrics metrics) {
603 return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
604 size, metrics));
Winson Chung5f8afe62013-08-12 16:19:28 -0700605 }
606
Winson Chungf7d45852013-10-10 18:57:15 -0700607 public DynamicGrid(Context context, Resources resources,
608 int minWidthPx, int minHeightPx,
Winson Chung892c74d2013-08-22 16:15:50 -0700609 int widthPx, int heightPx,
610 int awPx, int ahPx) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700611 DisplayMetrics dm = resources.getDisplayMetrics();
612 ArrayList<DeviceProfile> deviceProfiles =
613 new ArrayList<DeviceProfile>();
Winson Chungc58497e2013-09-03 17:48:37 -0700614 boolean hasAA = !AppsCustomizePagedView.DISABLE_ALL_APPS;
Winson Chung5f8afe62013-08-12 16:19:28 -0700615 // Our phone profiles include the bar sizes in each orientation
616 deviceProfiles.add(new DeviceProfile("Super Short Stubby",
Winson Chung2651d132013-10-01 16:28:15 -0700617 255, 300, 2, 3, 48, 13, (hasAA ? 5 : 4), 48));
Winson Chung5f8afe62013-08-12 16:19:28 -0700618 deviceProfiles.add(new DeviceProfile("Shorter Stubby",
Winson Chung2651d132013-10-01 16:28:15 -0700619 255, 400, 3, 3, 48, 13, (hasAA ? 5 : 4), 48));
Winson Chung5f8afe62013-08-12 16:19:28 -0700620 deviceProfiles.add(new DeviceProfile("Short Stubby",
Winson Chung2651d132013-10-01 16:28:15 -0700621 275, 420, 3, 4, 48, 13, (hasAA ? 5 : 4), 48));
Winson Chung5f8afe62013-08-12 16:19:28 -0700622 deviceProfiles.add(new DeviceProfile("Stubby",
Winson Chung2651d132013-10-01 16:28:15 -0700623 255, 450, 3, 4, 48, 13, (hasAA ? 5 : 4), 48));
Winson Chung5f8afe62013-08-12 16:19:28 -0700624 deviceProfiles.add(new DeviceProfile("Nexus S",
Winson Chung2651d132013-10-01 16:28:15 -0700625 296, 491.33f, 4, 4, 48, 13, (hasAA ? 5 : 4), 48));
Winson Chung5f8afe62013-08-12 16:19:28 -0700626 deviceProfiles.add(new DeviceProfile("Nexus 4",
Winson Chung2651d132013-10-01 16:28:15 -0700627 359, 518, 4, 4, 60, 13, (hasAA ? 5 : 4), 56));
Winson Chung5f8afe62013-08-12 16:19:28 -0700628 // The tablet profile is odd in that the landscape orientation
629 // also includes the nav bar on the side
630 deviceProfiles.add(new DeviceProfile("Nexus 7",
631 575, 904, 6, 6, 72, 14.4f, 7, 60));
632 // Larger tablet profiles always have system bars on the top & bottom
633 deviceProfiles.add(new DeviceProfile("Nexus 10",
634 727, 1207, 5, 8, 80, 14.4f, 9, 64));
635 /*
636 deviceProfiles.add(new DeviceProfile("Nexus 7",
637 600, 960, 5, 5, 72, 14.4f, 5, 60));
638 deviceProfiles.add(new DeviceProfile("Nexus 10",
Winson Chungc58497e2013-09-03 17:48:37 -0700639 800, 1280, 5, 5, 80, 14.4f, (hasAA ? 7 : 6), 64));
Winson Chung5f8afe62013-08-12 16:19:28 -0700640 */
641 deviceProfiles.add(new DeviceProfile("20-inch Tablet",
642 1527, 2527, 7, 7, 100, 20, 7, 72));
643 mMinWidth = dpiFromPx(minWidthPx, dm);
644 mMinHeight = dpiFromPx(minHeightPx, dm);
Winson Chungf7d45852013-10-10 18:57:15 -0700645 mProfile = new DeviceProfile(context, deviceProfiles,
Winson Chung892c74d2013-08-22 16:15:50 -0700646 mMinWidth, mMinHeight,
Winson Chung5f8afe62013-08-12 16:19:28 -0700647 widthPx, heightPx,
Winson Chung892c74d2013-08-22 16:15:50 -0700648 awPx, ahPx,
Winson Chung5f8afe62013-08-12 16:19:28 -0700649 resources);
650 }
651
652 DeviceProfile getDeviceProfile() {
653 return mProfile;
654 }
655
656 public String toString() {
657 return "-------- DYNAMIC GRID ------- \n" +
658 "Wd: " + mProfile.minWidthDps + ", Hd: " + mProfile.minHeightDps +
659 ", W: " + mProfile.widthPx + ", H: " + mProfile.heightPx +
660 " [r: " + mProfile.numRows + ", c: " + mProfile.numColumns +
Winson Chung6e1c0d32013-10-25 15:24:24 -0700661 ", is: " + mProfile.iconSizePx + ", its: " + mProfile.iconTextSizePx +
Winson Chung5f8afe62013-08-12 16:19:28 -0700662 ", cw: " + mProfile.cellWidthPx + ", ch: " + mProfile.cellHeightPx +
663 ", hc: " + mProfile.numHotseatIcons + ", his: " + mProfile.hotseatIconSizePx + "]";
664 }
665}