blob: 918517ebd34e2d4cae0b3bef6048d65b48a24a3c [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;
Adam Cohen59400422014-03-05 18:07:04 -080072 public float numRows;
73 public float numColumns;
Winson Chungb3800242013-10-24 11:01:54 -070074 float numHotseatIcons;
Adam Cohen4ae96ce2014-08-29 15:05:48 -070075 float iconSize;
Winson Chungb3800242013-10-24 11:01:54 -070076 private float iconTextSize;
77 private int iconDrawablePaddingOriginalPx;
78 private float hotseatIconSize;
79
Winson Chungbe876472014-05-14 14:15:20 -070080 int defaultLayoutId;
Winson Chungbe876472014-05-14 14:15:20 -070081
Winson Chungb3800242013-10-24 11:01:54 -070082 boolean isLandscape;
83 boolean isTablet;
84 boolean isLargeTablet;
Hyunyoung Songada50982015-04-10 14:35:23 -070085 public boolean isLayoutRtl;
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -070086
Winson Chungb3800242013-10-24 11:01:54 -070087 boolean transposeLayoutWithOrientation;
88
89 int desiredWorkspaceLeftRightMarginPx;
Adam Cohen59400422014-03-05 18:07:04 -080090 public int edgeMarginPx;
Winson Chungb3800242013-10-24 11:01:54 -070091 Rect defaultWidgetPadding;
92
93 int widthPx;
94 int heightPx;
Adam Cohen59400422014-03-05 18:07:04 -080095 public int availableWidthPx;
96 public int availableHeightPx;
Winson Chungb3800242013-10-24 11:01:54 -070097 int defaultPageSpacingPx;
98
99 int overviewModeMinIconZoneHeightPx;
100 int overviewModeMaxIconZoneHeightPx;
Jorim Jaggid017f882014-01-14 17:08:48 -0800101 int overviewModeBarItemWidthPx;
102 int overviewModeBarSpacerWidthPx;
Winson Chungb3800242013-10-24 11:01:54 -0700103 float overviewModeIconZoneRatio;
104 float overviewModeScaleFactor;
105
Adam Cohen59400422014-03-05 18:07:04 -0800106 public int cellWidthPx;
107 public int cellHeightPx;
108
Hyunyoung Song3f471442015-04-08 19:01:34 -0700109 public int iconSizePx;
110 public int iconTextSizePx;
Winson Chungb3800242013-10-24 11:01:54 -0700111 int iconDrawablePaddingPx;
Winson Chungb3800242013-10-24 11:01:54 -0700112 int allAppsIconSizePx;
113 int allAppsIconTextSizePx;
114 int allAppsCellWidthPx;
115 int allAppsCellHeightPx;
116 int allAppsCellPaddingPx;
117 int folderBackgroundOffset;
118 int folderIconSizePx;
119 int folderCellWidthPx;
120 int folderCellHeightPx;
121 int hotseatCellWidthPx;
122 int hotseatCellHeightPx;
123 int hotseatIconSizePx;
124 int hotseatBarHeightPx;
125 int hotseatAllAppsRank;
126 int allAppsNumRows;
127 int allAppsNumCols;
Winson Chung93f98ea2015-03-10 16:28:47 -0700128 int appsViewNumCols;
Winson Chungb3800242013-10-24 11:01:54 -0700129 int searchBarSpaceWidthPx;
Winson Chungb3800242013-10-24 11:01:54 -0700130 int searchBarSpaceHeightPx;
Winson Chungb3800242013-10-24 11:01:54 -0700131 int pageIndicatorHeightPx;
Adam Cohen63f1ec02014-08-12 09:23:13 -0700132 int allAppsButtonVisualSize;
Winson Chungb3800242013-10-24 11:01:54 -0700133
Winson Chung59a488a2013-12-10 12:32:14 -0800134 float dragViewScale;
135
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700136 int allAppsShortEdgeCount = -1;
137 int allAppsLongEdgeCount = -1;
138
Winson Chungb3800242013-10-24 11:01:54 -0700139 private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
140
141 DeviceProfile(String n, float w, float h, float r, float c,
Adam Cohencee8c662014-10-16 09:49:52 -0700142 float is, float its, float hs, float his, int dlId) {
Winson Chungb3800242013-10-24 11:01:54 -0700143 // Ensure that we have an odd number of hotseat items (since we need to place all apps)
Sunny Goyalc9acdd52015-02-26 12:34:42 -0800144 if (hs % 2 == 0) {
Winson Chungb3800242013-10-24 11:01:54 -0700145 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
146 }
147
148 name = n;
149 minWidthDps = w;
150 minHeightDps = h;
151 numRows = r;
152 numColumns = c;
153 iconSize = is;
154 iconTextSize = its;
155 numHotseatIcons = hs;
156 hotseatIconSize = his;
Winson Chungbe876472014-05-14 14:15:20 -0700157 defaultLayoutId = dlId;
Winson Chungb3800242013-10-24 11:01:54 -0700158 }
159
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700160 DeviceProfile() {
161 }
162
Winson Chungb3800242013-10-24 11:01:54 -0700163 DeviceProfile(Context context,
164 ArrayList<DeviceProfile> profiles,
165 float minWidth, float minHeight,
166 int wPx, int hPx,
167 int awPx, int ahPx,
168 Resources res) {
169 DisplayMetrics dm = res.getDisplayMetrics();
170 ArrayList<DeviceProfileQuery> points =
171 new ArrayList<DeviceProfileQuery>();
172 transposeLayoutWithOrientation =
173 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
174 minWidthDps = minWidth;
175 minHeightDps = minHeight;
176
177 ComponentName cn = new ComponentName(context.getPackageName(),
178 this.getClass().getName());
179 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
180 edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
181 desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
182 pageIndicatorHeightPx =
183 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
184 defaultPageSpacingPx =
185 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
186 allAppsCellPaddingPx =
187 res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
188 overviewModeMinIconZoneHeightPx =
189 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
190 overviewModeMaxIconZoneHeightPx =
191 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
Jorim Jaggid017f882014-01-14 17:08:48 -0800192 overviewModeBarItemWidthPx =
193 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
194 overviewModeBarSpacerWidthPx =
195 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
Winson Chungb3800242013-10-24 11:01:54 -0700196 overviewModeIconZoneRatio =
197 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
198 overviewModeScaleFactor =
199 res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
200
Winson Chungbe876472014-05-14 14:15:20 -0700201 // Find the closes profile given the width/height
Winson Chungb3800242013-10-24 11:01:54 -0700202 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700203 points.add(new DeviceProfileQuery(p, 0f));
Winson Chungb3800242013-10-24 11:01:54 -0700204 }
Winson Chungbe876472014-05-14 14:15:20 -0700205 DeviceProfile closestProfile = findClosestDeviceProfile(minWidth, minHeight, points);
206
207 // Snap to the closest row count
208 numRows = closestProfile.numRows;
209
210 // Snap to the closest column count
211 numColumns = closestProfile.numColumns;
212
213 // Snap to the closest hotseat size
214 numHotseatIcons = closestProfile.numHotseatIcons;
Winson Chungb3800242013-10-24 11:01:54 -0700215 hotseatAllAppsRank = (int) (numHotseatIcons / 2);
216
Winson Chungbe876472014-05-14 14:15:20 -0700217 // Snap to the closest default layout id
218 defaultLayoutId = closestProfile.defaultLayoutId;
219
Winson Chungb3800242013-10-24 11:01:54 -0700220 // Interpolate the icon size
221 points.clear();
222 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700223 points.add(new DeviceProfileQuery(p, p.iconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700224 }
225 iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700226
Winson Chungb3800242013-10-24 11:01:54 -0700227 // AllApps uses the original non-scaled icon size
228 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
229
230 // Interpolate the icon text size
231 points.clear();
232 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700233 points.add(new DeviceProfileQuery(p, p.iconTextSize));
Winson Chungb3800242013-10-24 11:01:54 -0700234 }
235 iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
236 iconDrawablePaddingOriginalPx =
237 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
238 // AllApps uses the original non-scaled icon text size
239 allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
240
241 // Interpolate the hotseat icon size
242 points.clear();
243 for (DeviceProfile p : profiles) {
Winson Chungbe876472014-05-14 14:15:20 -0700244 points.add(new DeviceProfileQuery(p, p.hotseatIconSize));
Winson Chungb3800242013-10-24 11:01:54 -0700245 }
246 // Hotseat
247 hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
248
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700249 // If the partner customization apk contains any grid overrides, apply them
250 applyPartnerDeviceProfileOverrides(context, dm);
251
Winson Chungb3800242013-10-24 11:01:54 -0700252 // Calculate the remaining vars
253 updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
254 updateAvailableDimensions(context);
Adam Cohen63f1ec02014-08-12 09:23:13 -0700255 computeAllAppsButtonSize(context);
256 }
257
258 /**
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700259 * Apply any Partner customization grid overrides.
260 *
261 * Currently we support: all apps row / column count.
262 */
263 private void applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm) {
264 Partner p = Partner.get(ctx.getPackageManager());
265 if (p != null) {
266 DeviceProfile partnerDp = p.getDeviceProfileOverride(dm);
267 if (partnerDp != null) {
268 if (partnerDp.numRows > 0 && partnerDp.numColumns > 0) {
269 numRows = partnerDp.numRows;
270 numColumns = partnerDp.numColumns;
271 }
272 if (partnerDp.allAppsShortEdgeCount > 0 && partnerDp.allAppsLongEdgeCount > 0) {
273 allAppsShortEdgeCount = partnerDp.allAppsShortEdgeCount;
274 allAppsLongEdgeCount = partnerDp.allAppsLongEdgeCount;
275 }
276 if (partnerDp.iconSize > 0) {
277 iconSize = partnerDp.iconSize;
278 // AllApps uses the original non-scaled icon size
279 allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
280 }
281 }
282 }
283 }
284
285 /**
Adam Cohen63f1ec02014-08-12 09:23:13 -0700286 * Determine the exact visual footprint of the all apps button, taking into account scaling
287 * and internal padding of the drawable.
288 */
289 private void computeAllAppsButtonSize(Context context) {
290 Resources res = context.getResources();
291 float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
292 LauncherAppState app = LauncherAppState.getInstance();
293 allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding));
Winson Chungb3800242013-10-24 11:01:54 -0700294 }
295
296 void addCallback(DeviceProfileCallbacks cb) {
297 mCallbacks.add(cb);
298 cb.onAvailableSizeChanged(this);
299 }
300 void removeCallback(DeviceProfileCallbacks cb) {
301 mCallbacks.remove(cb);
302 }
303
304 private int getDeviceOrientation(Context context) {
305 WindowManager windowManager = (WindowManager)
306 context.getSystemService(Context.WINDOW_SERVICE);
307 Resources resources = context.getResources();
308 DisplayMetrics dm = resources.getDisplayMetrics();
309 Configuration config = resources.getConfiguration();
310 int rotation = windowManager.getDefaultDisplay().getRotation();
311
312 boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
313 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
314 boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
315 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
316 if (isLandscape || isRotatedPortrait) {
317 return CellLayout.LANDSCAPE;
318 } else {
319 return CellLayout.PORTRAIT;
320 }
321 }
322
323 private void updateAvailableDimensions(Context context) {
324 WindowManager windowManager = (WindowManager)
325 context.getSystemService(Context.WINDOW_SERVICE);
326 Display display = windowManager.getDefaultDisplay();
327 Resources resources = context.getResources();
328 DisplayMetrics dm = resources.getDisplayMetrics();
329 Configuration config = resources.getConfiguration();
330
331 // There are three possible configurations that the dynamic grid accounts for, portrait,
332 // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
333 // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
334 // the height is the smallest height (either with the nav bar at the bottom or to the
335 // side) and otherwise, the height is simply the largest possible height for a portrait
336 // device.
337 Point size = new Point();
338 Point smallestSize = new Point();
339 Point largestSize = new Point();
340 display.getSize(size);
341 display.getCurrentSizeRange(smallestSize, largestSize);
342 availableWidthPx = size.x;
343 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
344 availableHeightPx = smallestSize.y;
345 } else {
346 availableHeightPx = largestSize.y;
347 }
348
349 // Check to see if the icons fit in the new available height. If not, then we need to
350 // shrink the icon size.
Winson Chungb3800242013-10-24 11:01:54 -0700351 float scale = 1f;
352 int drawablePadding = iconDrawablePaddingOriginalPx;
353 updateIconSize(1f, drawablePadding, resources, dm);
354 float usedHeight = (cellHeightPx * numRows);
Winson Chung59a488a2013-12-10 12:32:14 -0800355
356 Rect workspacePadding = getWorkspacePadding();
Winson Chungb3800242013-10-24 11:01:54 -0700357 int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
358 if (usedHeight > maxHeight) {
359 scale = maxHeight / usedHeight;
360 drawablePadding = 0;
361 }
362 updateIconSize(scale, drawablePadding, resources, dm);
363
364 // Make the callbacks
365 for (DeviceProfileCallbacks cb : mCallbacks) {
366 cb.onAvailableSizeChanged(this);
367 }
368 }
369
Winson Chung93f98ea2015-03-10 16:28:47 -0700370 private void updateIconSize(float scale, int drawablePadding, Resources res,
Winson Chungb3800242013-10-24 11:01:54 -0700371 DisplayMetrics dm) {
372 iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
373 iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
374 iconDrawablePaddingPx = drawablePadding;
375 hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
376
377 // Search Bar
Sunny Goyal594d76d2014-11-06 10:12:54 -0800378 searchBarSpaceWidthPx = Math.min(widthPx,
Winson Chung93f98ea2015-03-10 16:28:47 -0700379 res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
Sunny Goyal594d76d2014-11-06 10:12:54 -0800380 searchBarSpaceHeightPx = getSearchBarTopOffset()
Winson Chung93f98ea2015-03-10 16:28:47 -0700381 + res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
Winson Chungb3800242013-10-24 11:01:54 -0700382
383 // Calculate the actual text height
384 Paint textPaint = new Paint();
385 textPaint.setTextSize(iconTextSizePx);
386 FontMetrics fm = textPaint.getFontMetrics();
387 cellWidthPx = iconSizePx;
388 cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
Winson Chung93f98ea2015-03-10 16:28:47 -0700389 final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
Winson Chung59a488a2013-12-10 12:32:14 -0800390 dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
Winson Chungb3800242013-10-24 11:01:54 -0700391
392 // Hotseat
393 hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
394 hotseatCellWidthPx = iconSizePx;
395 hotseatCellHeightPx = iconSizePx;
396
397 // Folder
398 folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
399 folderCellHeightPx = cellHeightPx + edgeMarginPx;
400 folderBackgroundOffset = -edgeMarginPx;
401 folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
402
403 // All Apps
Winson Chungb3800242013-10-24 11:01:54 -0700404 allAppsCellWidthPx = allAppsIconSizePx;
405 allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
406 int maxLongEdgeCellCount =
Winson Chung93f98ea2015-03-10 16:28:47 -0700407 res.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
Winson Chungb3800242013-10-24 11:01:54 -0700408 int maxShortEdgeCellCount =
Winson Chung93f98ea2015-03-10 16:28:47 -0700409 res.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
Winson Chungb3800242013-10-24 11:01:54 -0700410 int minEdgeCellCount =
Winson Chung93f98ea2015-03-10 16:28:47 -0700411 res.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
Winson Chungb3800242013-10-24 11:01:54 -0700412 int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
413 int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
414
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700415 if (allAppsShortEdgeCount > 0 && allAppsLongEdgeCount > 0) {
416 allAppsNumRows = isLandscape ? allAppsShortEdgeCount : allAppsLongEdgeCount;
417 allAppsNumCols = isLandscape ? allAppsLongEdgeCount : allAppsShortEdgeCount;
418 } else {
419 allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
420 (allAppsCellHeightPx + allAppsCellPaddingPx);
421 allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
Winson Chung93f98ea2015-03-10 16:28:47 -0700422 allAppsNumCols = (availableWidthPx) / (allAppsCellWidthPx + allAppsCellPaddingPx);
Adam Cohen4ae96ce2014-08-29 15:05:48 -0700423 allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
424 }
Winson Chung93f98ea2015-03-10 16:28:47 -0700425
Winson Chung0f785722015-04-08 10:27:49 -0700426 int appsContainerViewWidthPx = res.getDimensionPixelSize(R.dimen.apps_container_width);
427 updateAppsViewNumCols(res, appsContainerViewWidthPx);
428 }
429
430 public boolean updateAppsViewNumCols(Resources res, int containerWidth) {
Winson Chung83f59ab2015-05-05 17:21:58 -0700431 if (AppsContainerView.GRID_HIDE_SECTION_HEADERS) {
432 if (appsViewNumCols != allAppsNumCols) {
433 appsViewNumCols = allAppsNumCols;
434 return true;
435 }
436 return false;
437 }
Winson Chung93f98ea2015-03-10 16:28:47 -0700438 int appsViewLeftMarginPx =
439 res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
Winson Chung0f785722015-04-08 10:27:49 -0700440 int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx;
441 int numCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
Winson Chungbd808532015-04-08 11:07:53 -0700442 (allAppsCellWidthPx + 2 * allAppsCellPaddingPx);
Winson Chung0f785722015-04-08 10:27:49 -0700443 if (numCols != appsViewNumCols) {
444 appsViewNumCols = numCols;
445 return true;
446 }
447 return false;
Winson Chungb3800242013-10-24 11:01:54 -0700448 }
449
450 void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
451 int awPx, int ahPx) {
Winson Chung42b3c062013-12-04 12:09:59 -0800452 Configuration configuration = resources.getConfiguration();
453 isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
Winson Chungb3800242013-10-24 11:01:54 -0700454 isTablet = resources.getBoolean(R.bool.is_tablet);
455 isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
Winson Chung6033ceb2014-02-05 12:37:42 -0800456 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
457 isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
458 } else {
459 isLayoutRtl = false;
460 }
Winson Chungb3800242013-10-24 11:01:54 -0700461 widthPx = wPx;
462 heightPx = hPx;
463 availableWidthPx = awPx;
464 availableHeightPx = ahPx;
465
466 updateAvailableDimensions(context);
467 }
468
Adam Cohen091440a2015-03-18 14:16:05 -0700469 @Thunk float dist(PointF p0, PointF p1) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700470 return (float) Math.hypot(p1.x - p0.x, p1.y - p0.y);
Winson Chungb3800242013-10-24 11:01:54 -0700471 }
472
473 private float weight(PointF a, PointF b,
474 float pow) {
475 float d = dist(a, b);
476 if (d == 0f) {
477 return Float.POSITIVE_INFINITY;
478 }
479 return (float) (1f / Math.pow(d, pow));
480 }
481
Winson Chungbe876472014-05-14 14:15:20 -0700482 /** Returns the closest device profile given the width and height and a list of profiles */
483 private DeviceProfile findClosestDeviceProfile(float width, float height,
484 ArrayList<DeviceProfileQuery> points) {
485 return findClosestDeviceProfiles(width, height, points).get(0).profile;
486 }
487
488 /** Returns the closest device profiles ordered by closeness to the specified width and height */
489 private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height,
490 ArrayList<DeviceProfileQuery> points) {
491 final PointF xy = new PointF(width, height);
492
493 // Sort the profiles by their closeness to the dimensions
494 ArrayList<DeviceProfileQuery> pointsByNearness = points;
495 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
496 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
497 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
498 }
499 });
500
501 return pointsByNearness;
502 }
503
Winson Chungb3800242013-10-24 11:01:54 -0700504 private float invDistWeightedInterpolate(float width, float height,
505 ArrayList<DeviceProfileQuery> points) {
506 float sum = 0;
507 float weights = 0;
508 float pow = 5;
509 float kNearestNeighbors = 3;
510 final PointF xy = new PointF(width, height);
511
Winson Chungbe876472014-05-14 14:15:20 -0700512 ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height,
513 points);
Winson Chungb3800242013-10-24 11:01:54 -0700514
515 for (int i = 0; i < pointsByNearness.size(); ++i) {
516 DeviceProfileQuery p = pointsByNearness.get(i);
517 if (i < kNearestNeighbors) {
518 float w = weight(xy, p.dimens, pow);
519 if (w == Float.POSITIVE_INFINITY) {
520 return p.value;
521 }
522 weights += w;
523 }
524 }
525
526 for (int i = 0; i < pointsByNearness.size(); ++i) {
527 DeviceProfileQuery p = pointsByNearness.get(i);
528 if (i < kNearestNeighbors) {
529 float w = weight(xy, p.dimens, pow);
530 sum += w * p.value / weights;
531 }
532 }
533
534 return sum;
535 }
536
Winson Chung69e04ea2013-12-02 14:43:44 -0800537 /** Returns the search bar top offset */
538 int getSearchBarTopOffset() {
539 if (isTablet() && !isVerticalBarLayout()) {
540 return 4 * edgeMarginPx;
541 } else {
542 return 2 * edgeMarginPx;
543 }
544 }
545
Winson Chungb3800242013-10-24 11:01:54 -0700546 /** Returns the search bar bounds in the current orientation */
547 Rect getSearchBarBounds() {
548 return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
549 }
550 /** Returns the search bar bounds in the specified orientation */
551 Rect getSearchBarBounds(int orientation) {
552 Rect bounds = new Rect();
553 if (orientation == CellLayout.LANDSCAPE &&
554 transposeLayoutWithOrientation) {
Winson Chung42b3c062013-12-04 12:09:59 -0800555 if (isLayoutRtl) {
556 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
557 availableWidthPx, availableHeightPx - edgeMarginPx);
558 } else {
559 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
560 availableHeightPx - edgeMarginPx);
561 }
Winson Chungb3800242013-10-24 11:01:54 -0700562 } else {
563 if (isTablet()) {
564 // Pad the left and right of the workspace to ensure consistent spacing
565 // between all icons
566 int width = (orientation == CellLayout.LANDSCAPE)
567 ? Math.max(widthPx, heightPx)
568 : Math.min(widthPx, heightPx);
569 // XXX: If the icon size changes across orientations, we will have to take
570 // that into account here too.
571 int gap = (int) ((width - 2 * edgeMarginPx -
572 (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
Winson Chung2cb24712013-12-02 15:00:39 -0800573 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
574 availableWidthPx - (edgeMarginPx + gap),
Winson Chungb3800242013-10-24 11:01:54 -0700575 searchBarSpaceHeightPx);
576 } else {
Winson Chung2cb24712013-12-02 15:00:39 -0800577 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
578 getSearchBarTopOffset(),
Winson Chungb3800242013-10-24 11:01:54 -0700579 availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
580 defaultWidgetPadding.right), searchBarSpaceHeightPx);
581 }
582 }
583 return bounds;
584 }
585
Winson Chunga6945242014-01-08 14:04:34 -0800586 /** Returns the bounds of the workspace page indicators. */
587 Rect getWorkspacePageIndicatorBounds(Rect insets) {
588 Rect workspacePadding = getWorkspacePadding();
Winson Chung205cd772014-01-15 14:31:59 -0800589 if (isLandscape && transposeLayoutWithOrientation) {
590 if (isLayoutRtl) {
591 return new Rect(workspacePadding.left, workspacePadding.top,
592 workspacePadding.left + pageIndicatorHeightPx,
593 heightPx - workspacePadding.bottom - insets.bottom);
594 } else {
595 int pageIndicatorLeft = widthPx - workspacePadding.right;
596 return new Rect(pageIndicatorLeft, workspacePadding.top,
597 pageIndicatorLeft + pageIndicatorHeightPx,
598 heightPx - workspacePadding.bottom - insets.bottom);
599 }
600 } else {
601 int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom;
602 return new Rect(workspacePadding.left, pageIndicatorTop,
603 widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx);
604 }
Winson Chunga6945242014-01-08 14:04:34 -0800605 }
606
Adam Cohen59400422014-03-05 18:07:04 -0800607 public int getWorkspaceGridHeight() {
608 Rect p = getWorkspacePadding();
609 return availableHeightPx - p.top - p.bottom;
610 }
611
612 public int getWorkspaceGridWidth() {
613 Rect p = getWorkspacePadding();
614 return availableWidthPx - p.left - p.right;
615 }
616
Winson Chungb3800242013-10-24 11:01:54 -0700617 /** Returns the workspace padding in the specified orientation */
618 Rect getWorkspacePadding() {
619 return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
620 }
621 Rect getWorkspacePadding(int orientation) {
622 Rect searchBarBounds = getSearchBarBounds(orientation);
623 Rect padding = new Rect();
624 if (orientation == CellLayout.LANDSCAPE &&
625 transposeLayoutWithOrientation) {
626 // Pad the left and right of the workspace with search/hotseat bar sizes
Winson Chung42b3c062013-12-04 12:09:59 -0800627 if (isLayoutRtl) {
628 padding.set(hotseatBarHeightPx, edgeMarginPx,
629 searchBarBounds.width(), edgeMarginPx);
630 } else {
631 padding.set(searchBarBounds.width(), edgeMarginPx,
632 hotseatBarHeightPx, edgeMarginPx);
633 }
Winson Chungb3800242013-10-24 11:01:54 -0700634 } else {
635 if (isTablet()) {
636 // Pad the left and right of the workspace to ensure consistent spacing
637 // between all icons
Winson Chung59a488a2013-12-10 12:32:14 -0800638 float gapScale = 1f + (dragViewScale - 1f) / 2f;
Winson Chungb3800242013-10-24 11:01:54 -0700639 int width = (orientation == CellLayout.LANDSCAPE)
640 ? Math.max(widthPx, heightPx)
641 : Math.min(widthPx, heightPx);
Winson Chung59a488a2013-12-10 12:32:14 -0800642 int height = (orientation != CellLayout.LANDSCAPE)
643 ? Math.max(widthPx, heightPx)
644 : Math.min(widthPx, heightPx);
645 int paddingTop = searchBarBounds.bottom;
646 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
647 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
648 (numColumns * gapScale * cellWidthPx)));
649 int availableHeight = Math.max(0, height - paddingTop - paddingBottom
650 - (int) (2 * numRows * cellHeightPx));
651 padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
652 availableWidth / 2, paddingBottom + availableHeight / 2);
Winson Chungb3800242013-10-24 11:01:54 -0700653 } else {
654 // Pad the top and bottom of the workspace with search/hotseat bar sizes
655 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
656 searchBarBounds.bottom,
657 desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
658 hotseatBarHeightPx + pageIndicatorHeightPx);
659 }
660 }
661 return padding;
662 }
663
664 int getWorkspacePageSpacing(int orientation) {
Winson Chung59a488a2013-12-10 12:32:14 -0800665 if ((orientation == CellLayout.LANDSCAPE &&
666 transposeLayoutWithOrientation) || isLargeTablet()) {
Winson Chungb3800242013-10-24 11:01:54 -0700667 // In landscape mode the page spacing is set to the default.
668 return defaultPageSpacingPx;
669 } else {
670 // In portrait, we want the pages spaced such that there is no
671 // overhang of the previous / next page into the current page viewport.
672 // We assume symmetrical padding in portrait mode.
Adam Cohenefb31e32014-01-16 16:07:50 -0800673 return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
Winson Chungb3800242013-10-24 11:01:54 -0700674 }
675 }
676
677 Rect getOverviewModeButtonBarRect() {
678 int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
679 zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
680 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
681 return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
682 }
683
684 float getOverviewModeScale() {
685 Rect workspacePadding = getWorkspacePadding();
686 Rect overviewBar = getOverviewModeButtonBarRect();
687 int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
688 return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
689 }
690
691 // The rect returned will be extended to below the system ui that covers the workspace
692 Rect getHotseatRect() {
693 if (isVerticalBarLayout()) {
694 return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
695 Integer.MAX_VALUE, availableHeightPx);
696 } else {
697 return new Rect(0, availableHeightPx - hotseatBarHeightPx,
698 availableWidthPx, Integer.MAX_VALUE);
699 }
700 }
701
702 int calculateCellWidth(int width, int countX) {
703 return width / countX;
704 }
705 int calculateCellHeight(int height, int countY) {
706 return height / countY;
707 }
708
709 boolean isPhone() {
710 return !isTablet && !isLargeTablet;
711 }
712 boolean isTablet() {
713 return isTablet;
714 }
715 boolean isLargeTablet() {
716 return isLargeTablet;
717 }
718
Hyunyoung Song18bfaaf2015-03-17 11:32:21 -0700719 /**
720 * When {@code true}, hotseat is on the bottom row when in landscape mode.
721 * If {@code false}, hotseat is on the right column when in landscape mode.
722 */
Winson Chungb3800242013-10-24 11:01:54 -0700723 boolean isVerticalBarLayout() {
724 return isLandscape && transposeLayoutWithOrientation;
725 }
726
727 boolean shouldFadeAdjacentWorkspaceScreens() {
728 return isVerticalBarLayout() || isLargeTablet();
729 }
730
Jorim Jaggid017f882014-01-14 17:08:48 -0800731 int getVisibleChildCount(ViewGroup parent) {
732 int visibleChildren = 0;
733 for (int i = 0; i < parent.getChildCount(); i++) {
734 if (parent.getChildAt(i).getVisibility() != View.GONE) {
735 visibleChildren++;
736 }
737 }
738 return visibleChildren;
739 }
740
Winson Chungb3800242013-10-24 11:01:54 -0700741 public void layout(Launcher launcher) {
742 FrameLayout.LayoutParams lp;
Winson Chungb3800242013-10-24 11:01:54 -0700743 boolean hasVerticalBarLayout = isVerticalBarLayout();
744
745 // Layout the search bar space
746 View searchBar = launcher.getSearchBar();
747 lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
748 if (hasVerticalBarLayout) {
Winson Chung69e04ea2013-12-02 14:43:44 -0800749 // Vertical search bar space
Sunny Goyald9cc7802015-04-23 14:47:19 -0700750 lp.gravity = Gravity.LEFT;
Winson Chungb3800242013-10-24 11:01:54 -0700751 lp.width = searchBarSpaceHeightPx;
Adam Cohen24ce0b32014-01-14 16:18:14 -0800752
753 LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
754 targets.setOrientation(LinearLayout.VERTICAL);
Sunny Goyald9cc7802015-04-23 14:47:19 -0700755 FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams();
756 targetsLp.gravity = Gravity.TOP;
757 targetsLp.height = LayoutParams.WRAP_CONTENT;
758
Winson Chungb3800242013-10-24 11:01:54 -0700759 } else {
Winson Chung69e04ea2013-12-02 14:43:44 -0800760 // Horizontal search bar space
Sunny Goyald9cc7802015-04-23 14:47:19 -0700761 lp.gravity = Gravity.TOP;
Winson Chungb3800242013-10-24 11:01:54 -0700762 lp.height = searchBarSpaceHeightPx;
Sunny Goyald9cc7802015-04-23 14:47:19 -0700763
764 LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
765 targets.getLayoutParams().width = searchBarSpaceWidthPx;
Winson Chungb3800242013-10-24 11:01:54 -0700766 }
767 searchBar.setLayoutParams(lp);
768
Winson Chungb3800242013-10-24 11:01:54 -0700769 // Layout the workspace
770 PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
771 lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
772 lp.gravity = Gravity.CENTER;
773 int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
774 Rect padding = getWorkspacePadding(orientation);
775 workspace.setLayoutParams(lp);
776 workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
777 workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
778
779 // Layout the hotseat
780 View hotseat = launcher.findViewById(R.id.hotseat);
781 lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
782 if (hasVerticalBarLayout) {
783 // Vertical hotseat
Winson Chung42b3c062013-12-04 12:09:59 -0800784 lp.gravity = Gravity.END;
Winson Chungb3800242013-10-24 11:01:54 -0700785 lp.width = hotseatBarHeightPx;
786 lp.height = LayoutParams.MATCH_PARENT;
787 hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
788 } else if (isTablet()) {
Winson Chung59a488a2013-12-10 12:32:14 -0800789 // Pad the hotseat with the workspace padding calculated above
Winson Chungb3800242013-10-24 11:01:54 -0700790 lp.gravity = Gravity.BOTTOM;
791 lp.width = LayoutParams.MATCH_PARENT;
792 lp.height = hotseatBarHeightPx;
Winson Chung59a488a2013-12-10 12:32:14 -0800793 hotseat.setPadding(edgeMarginPx + padding.left, 0,
794 edgeMarginPx + padding.right,
Winson Chungb3800242013-10-24 11:01:54 -0700795 2 * edgeMarginPx);
796 } else {
797 // For phones, layout the hotseat without any bottom margin
798 // to ensure that we have space for the folders
799 lp.gravity = Gravity.BOTTOM;
800 lp.width = LayoutParams.MATCH_PARENT;
801 lp.height = hotseatBarHeightPx;
Sunny Goyale0bca382015-04-06 18:39:22 +0000802 hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
803 2 * edgeMarginPx, 0);
Winson Chungb3800242013-10-24 11:01:54 -0700804 }
805 hotseat.setLayoutParams(lp);
806
807 // Layout the page indicators
808 View pageIndicator = launcher.findViewById(R.id.page_indicator);
809 if (pageIndicator != null) {
810 if (hasVerticalBarLayout) {
811 // Hide the page indicators when we have vertical search/hotseat
812 pageIndicator.setVisibility(View.GONE);
813 } else {
814 // Put the page indicators above the hotseat
815 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
816 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
817 lp.width = LayoutParams.WRAP_CONTENT;
818 lp.height = LayoutParams.WRAP_CONTENT;
819 lp.bottomMargin = hotseatBarHeightPx;
820 pageIndicator.setLayoutParams(lp);
821 }
822 }
823
Winson Chungb3800242013-10-24 11:01:54 -0700824 // Layout the Overview Mode
Jorim Jaggid017f882014-01-14 17:08:48 -0800825 ViewGroup overviewMode = launcher.getOverviewPanel();
Winson Chungb3800242013-10-24 11:01:54 -0700826 if (overviewMode != null) {
827 Rect r = getOverviewModeButtonBarRect();
828 lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
829 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
Sunny Goyal8dfe2da2014-10-24 16:26:52 -0700830
831 int visibleChildCount = getVisibleChildCount(overviewMode);
832 int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
833 int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
834
835 lp.width = Math.min(availableWidthPx, maxWidth);
Winson Chungb3800242013-10-24 11:01:54 -0700836 lp.height = r.height();
837 overviewMode.setLayoutParams(lp);
Sunny Goyal8dfe2da2014-10-24 16:26:52 -0700838
839 if (lp.width > totalItemWidth && visibleChildCount > 1) {
840 // We have enough space. Lets add some margin too.
841 int margin = (lp.width - totalItemWidth) / (visibleChildCount-1);
842 View lastChild = null;
843
844 // Set margin of all visible children except the last visible child
845 for (int i = 0; i < visibleChildCount; i++) {
846 if (lastChild != null) {
847 MarginLayoutParams clp = (MarginLayoutParams) lastChild.getLayoutParams();
848 if (isLayoutRtl) {
849 clp.leftMargin = margin;
850 } else {
851 clp.rightMargin = margin;
852 }
853 lastChild.setLayoutParams(clp);
854 lastChild = null;
855 }
856 View thisChild = overviewMode.getChildAt(i);
857 if (thisChild.getVisibility() != View.GONE) {
858 lastChild = thisChild;
859 }
860 }
861 }
Winson Chungb3800242013-10-24 11:01:54 -0700862 }
863 }
864}