• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.launcher3;
18 
19 import android.appwidget.AppWidgetHostView;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.res.Configuration;
23 import android.content.res.Resources;
24 import android.graphics.Paint;
25 import android.graphics.Paint.FontMetrics;
26 import android.graphics.Point;
27 import android.graphics.PointF;
28 import android.graphics.Rect;
29 import android.util.DisplayMetrics;
30 import android.view.Display;
31 import android.view.Gravity;
32 import android.view.Surface;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.view.ViewGroup.LayoutParams;
36 import android.view.WindowManager;
37 import android.widget.FrameLayout;
38 import android.widget.LinearLayout;
39 
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.Comparator;
43 
44 
45 class DeviceProfileQuery {
46     float widthDps;
47     float heightDps;
48     float value;
49     PointF dimens;
50 
DeviceProfileQuery(float w, float h, float v)51     DeviceProfileQuery(float w, float h, float v) {
52         widthDps = w;
53         heightDps = h;
54         value = v;
55         dimens = new PointF(w, h);
56     }
57 }
58 
59 public class DeviceProfile {
60     public static interface DeviceProfileCallbacks {
onAvailableSizeChanged(DeviceProfile grid)61         public void onAvailableSizeChanged(DeviceProfile grid);
62     }
63 
64     String name;
65     float minWidthDps;
66     float minHeightDps;
67     float numRows;
68     float numColumns;
69     float numHotseatIcons;
70     private float iconSize;
71     private float iconTextSize;
72     private int iconDrawablePaddingOriginalPx;
73     private float hotseatIconSize;
74 
75     boolean isLandscape;
76     boolean isTablet;
77     boolean isLargeTablet;
78     boolean isLayoutRtl;
79     boolean transposeLayoutWithOrientation;
80 
81     int desiredWorkspaceLeftRightMarginPx;
82     int edgeMarginPx;
83     Rect defaultWidgetPadding;
84 
85     int widthPx;
86     int heightPx;
87     int availableWidthPx;
88     int availableHeightPx;
89     int defaultPageSpacingPx;
90 
91     int overviewModeMinIconZoneHeightPx;
92     int overviewModeMaxIconZoneHeightPx;
93     int overviewModeBarItemWidthPx;
94     int overviewModeBarSpacerWidthPx;
95     float overviewModeIconZoneRatio;
96     float overviewModeScaleFactor;
97 
98     int iconSizePx;
99     int iconTextSizePx;
100     int iconDrawablePaddingPx;
101     int cellWidthPx;
102     int cellHeightPx;
103     int allAppsIconSizePx;
104     int allAppsIconTextSizePx;
105     int allAppsCellWidthPx;
106     int allAppsCellHeightPx;
107     int allAppsCellPaddingPx;
108     int folderBackgroundOffset;
109     int folderIconSizePx;
110     int folderCellWidthPx;
111     int folderCellHeightPx;
112     int hotseatCellWidthPx;
113     int hotseatCellHeightPx;
114     int hotseatIconSizePx;
115     int hotseatBarHeightPx;
116     int hotseatAllAppsRank;
117     int allAppsNumRows;
118     int allAppsNumCols;
119     int searchBarSpaceWidthPx;
120     int searchBarSpaceMaxWidthPx;
121     int searchBarSpaceHeightPx;
122     int searchBarHeightPx;
123     int pageIndicatorHeightPx;
124 
125     float dragViewScale;
126 
127     private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
128 
DeviceProfile(String n, float w, float h, float r, float c, float is, float its, float hs, float his)129     DeviceProfile(String n, float w, float h, float r, float c,
130                   float is, float its, float hs, float his) {
131         // Ensure that we have an odd number of hotseat items (since we need to place all apps)
132         if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) {
133             throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
134         }
135 
136         name = n;
137         minWidthDps = w;
138         minHeightDps = h;
139         numRows = r;
140         numColumns = c;
141         iconSize = is;
142         iconTextSize = its;
143         numHotseatIcons = hs;
144         hotseatIconSize = his;
145     }
146 
DeviceProfile(Context context, ArrayList<DeviceProfile> profiles, float minWidth, float minHeight, int wPx, int hPx, int awPx, int ahPx, Resources res)147     DeviceProfile(Context context,
148                   ArrayList<DeviceProfile> profiles,
149                   float minWidth, float minHeight,
150                   int wPx, int hPx,
151                   int awPx, int ahPx,
152                   Resources res) {
153         DisplayMetrics dm = res.getDisplayMetrics();
154         ArrayList<DeviceProfileQuery> points =
155                 new ArrayList<DeviceProfileQuery>();
156         transposeLayoutWithOrientation =
157                 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
158         minWidthDps = minWidth;
159         minHeightDps = minHeight;
160 
161         ComponentName cn = new ComponentName(context.getPackageName(),
162                 this.getClass().getName());
163         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
164         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
165         desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
166         pageIndicatorHeightPx =
167                 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
168         defaultPageSpacingPx =
169                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
170         allAppsCellPaddingPx =
171                 res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
172         overviewModeMinIconZoneHeightPx =
173                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
174         overviewModeMaxIconZoneHeightPx =
175                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
176         overviewModeBarItemWidthPx =
177                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
178         overviewModeBarSpacerWidthPx =
179                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
180         overviewModeIconZoneRatio =
181                 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
182         overviewModeScaleFactor =
183                 res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
184 
185         // Interpolate the rows
186         for (DeviceProfile p : profiles) {
187             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
188         }
189         numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
190         // Interpolate the columns
191         points.clear();
192         for (DeviceProfile p : profiles) {
193             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
194         }
195         numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
196         // Interpolate the hotseat length
197         points.clear();
198         for (DeviceProfile p : profiles) {
199             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
200         }
201         numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
202         hotseatAllAppsRank = (int) (numHotseatIcons / 2);
203 
204         // Interpolate the icon size
205         points.clear();
206         for (DeviceProfile p : profiles) {
207             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
208         }
209         iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
210         // AllApps uses the original non-scaled icon size
211         allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
212 
213         // Interpolate the icon text size
214         points.clear();
215         for (DeviceProfile p : profiles) {
216             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
217         }
218         iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
219         iconDrawablePaddingOriginalPx =
220                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
221         // AllApps uses the original non-scaled icon text size
222         allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
223 
224         // Interpolate the hotseat icon size
225         points.clear();
226         for (DeviceProfile p : profiles) {
227             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
228         }
229         // Hotseat
230         hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
231 
232         // Calculate the remaining vars
233         updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
234         updateAvailableDimensions(context);
235     }
236 
addCallback(DeviceProfileCallbacks cb)237     void addCallback(DeviceProfileCallbacks cb) {
238         mCallbacks.add(cb);
239         cb.onAvailableSizeChanged(this);
240     }
removeCallback(DeviceProfileCallbacks cb)241     void removeCallback(DeviceProfileCallbacks cb) {
242         mCallbacks.remove(cb);
243     }
244 
getDeviceOrientation(Context context)245     private int getDeviceOrientation(Context context) {
246         WindowManager windowManager =  (WindowManager)
247                 context.getSystemService(Context.WINDOW_SERVICE);
248         Resources resources = context.getResources();
249         DisplayMetrics dm = resources.getDisplayMetrics();
250         Configuration config = resources.getConfiguration();
251         int rotation = windowManager.getDefaultDisplay().getRotation();
252 
253         boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
254                 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
255         boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
256                 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
257         if (isLandscape || isRotatedPortrait) {
258             return CellLayout.LANDSCAPE;
259         } else {
260             return CellLayout.PORTRAIT;
261         }
262     }
263 
updateAvailableDimensions(Context context)264     private void updateAvailableDimensions(Context context) {
265         WindowManager windowManager =  (WindowManager)
266                 context.getSystemService(Context.WINDOW_SERVICE);
267         Display display = windowManager.getDefaultDisplay();
268         Resources resources = context.getResources();
269         DisplayMetrics dm = resources.getDisplayMetrics();
270         Configuration config = resources.getConfiguration();
271 
272         // There are three possible configurations that the dynamic grid accounts for, portrait,
273         // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
274         // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
275         // the height is the smallest height (either with the nav bar at the bottom or to the
276         // side) and otherwise, the height is simply the largest possible height for a portrait
277         // device.
278         Point size = new Point();
279         Point smallestSize = new Point();
280         Point largestSize = new Point();
281         display.getSize(size);
282         display.getCurrentSizeRange(smallestSize, largestSize);
283         availableWidthPx = size.x;
284         if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
285             availableHeightPx = smallestSize.y;
286         } else {
287             availableHeightPx = largestSize.y;
288         }
289 
290         // Check to see if the icons fit in the new available height.  If not, then we need to
291         // shrink the icon size.
292         float scale = 1f;
293         int drawablePadding = iconDrawablePaddingOriginalPx;
294         updateIconSize(1f, drawablePadding, resources, dm);
295         float usedHeight = (cellHeightPx * numRows);
296 
297         Rect workspacePadding = getWorkspacePadding();
298         int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
299         if (usedHeight > maxHeight) {
300             scale = maxHeight / usedHeight;
301             drawablePadding = 0;
302         }
303         updateIconSize(scale, drawablePadding, resources, dm);
304 
305         // Make the callbacks
306         for (DeviceProfileCallbacks cb : mCallbacks) {
307             cb.onAvailableSizeChanged(this);
308         }
309     }
310 
updateIconSize(float scale, int drawablePadding, Resources resources, DisplayMetrics dm)311     private void updateIconSize(float scale, int drawablePadding, Resources resources,
312                                 DisplayMetrics dm) {
313         iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
314         iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
315         iconDrawablePaddingPx = drawablePadding;
316         hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
317 
318         // Search Bar
319         searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
320         searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
321         searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
322         searchBarSpaceHeightPx = searchBarHeightPx + getSearchBarTopOffset();
323 
324         // Calculate the actual text height
325         Paint textPaint = new Paint();
326         textPaint.setTextSize(iconTextSizePx);
327         FontMetrics fm = textPaint.getFontMetrics();
328         cellWidthPx = iconSizePx;
329         cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
330         final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale);
331         dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
332 
333         // Hotseat
334         hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
335         hotseatCellWidthPx = iconSizePx;
336         hotseatCellHeightPx = iconSizePx;
337 
338         // Folder
339         folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
340         folderCellHeightPx = cellHeightPx + edgeMarginPx;
341         folderBackgroundOffset = -edgeMarginPx;
342         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
343 
344         // All Apps
345         Rect padding = getWorkspacePadding(isLandscape ?
346                 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
347         int pageIndicatorOffset =
348                 resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
349         allAppsCellWidthPx = allAppsIconSizePx;
350         allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
351         int maxLongEdgeCellCount =
352                 resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
353         int maxShortEdgeCellCount =
354                 resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
355         int minEdgeCellCount =
356                 resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
357         int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
358         int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
359 
360         allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
361                 (allAppsCellHeightPx + allAppsCellPaddingPx);
362         allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
363         allAppsNumCols = (availableWidthPx) /
364                 (allAppsCellWidthPx + allAppsCellPaddingPx);
365         allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
366     }
367 
updateFromConfiguration(Context context, Resources resources, int wPx, int hPx, int awPx, int ahPx)368     void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
369                                  int awPx, int ahPx) {
370         Configuration configuration = resources.getConfiguration();
371         isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
372         isTablet = resources.getBoolean(R.bool.is_tablet);
373         isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
374         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
375             isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
376         } else {
377             isLayoutRtl = false;
378         }
379         widthPx = wPx;
380         heightPx = hPx;
381         availableWidthPx = awPx;
382         availableHeightPx = ahPx;
383 
384         updateAvailableDimensions(context);
385     }
386 
dist(PointF p0, PointF p1)387     private float dist(PointF p0, PointF p1) {
388         return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
389                 (p1.y-p0.y)*(p1.y-p0.y));
390     }
391 
weight(PointF a, PointF b, float pow)392     private float weight(PointF a, PointF b,
393                         float pow) {
394         float d = dist(a, b);
395         if (d == 0f) {
396             return Float.POSITIVE_INFINITY;
397         }
398         return (float) (1f / Math.pow(d, pow));
399     }
400 
invDistWeightedInterpolate(float width, float height, ArrayList<DeviceProfileQuery> points)401     private float invDistWeightedInterpolate(float width, float height,
402                 ArrayList<DeviceProfileQuery> points) {
403         float sum = 0;
404         float weights = 0;
405         float pow = 5;
406         float kNearestNeighbors = 3;
407         final PointF xy = new PointF(width, height);
408 
409         ArrayList<DeviceProfileQuery> pointsByNearness = points;
410         Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
411             public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
412                 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
413             }
414         });
415 
416         for (int i = 0; i < pointsByNearness.size(); ++i) {
417             DeviceProfileQuery p = pointsByNearness.get(i);
418             if (i < kNearestNeighbors) {
419                 float w = weight(xy, p.dimens, pow);
420                 if (w == Float.POSITIVE_INFINITY) {
421                     return p.value;
422                 }
423                 weights += w;
424             }
425         }
426 
427         for (int i = 0; i < pointsByNearness.size(); ++i) {
428             DeviceProfileQuery p = pointsByNearness.get(i);
429             if (i < kNearestNeighbors) {
430                 float w = weight(xy, p.dimens, pow);
431                 sum += w * p.value / weights;
432             }
433         }
434 
435         return sum;
436     }
437 
438     /** Returns the search bar top offset */
getSearchBarTopOffset()439     int getSearchBarTopOffset() {
440         if (isTablet() && !isVerticalBarLayout()) {
441             return 4 * edgeMarginPx;
442         } else {
443             return 2 * edgeMarginPx;
444         }
445     }
446 
447     /** Returns the search bar bounds in the current orientation */
getSearchBarBounds()448     Rect getSearchBarBounds() {
449         return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
450     }
451     /** Returns the search bar bounds in the specified orientation */
getSearchBarBounds(int orientation)452     Rect getSearchBarBounds(int orientation) {
453         Rect bounds = new Rect();
454         if (orientation == CellLayout.LANDSCAPE &&
455                 transposeLayoutWithOrientation) {
456             if (isLayoutRtl) {
457                 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
458                         availableWidthPx, availableHeightPx - edgeMarginPx);
459             } else {
460                 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
461                         availableHeightPx - edgeMarginPx);
462             }
463         } else {
464             if (isTablet()) {
465                 // Pad the left and right of the workspace to ensure consistent spacing
466                 // between all icons
467                 int width = (orientation == CellLayout.LANDSCAPE)
468                         ? Math.max(widthPx, heightPx)
469                         : Math.min(widthPx, heightPx);
470                 // XXX: If the icon size changes across orientations, we will have to take
471                 //      that into account here too.
472                 int gap = (int) ((width - 2 * edgeMarginPx -
473                         (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
474                 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
475                         availableWidthPx - (edgeMarginPx + gap),
476                         searchBarSpaceHeightPx);
477             } else {
478                 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
479                         getSearchBarTopOffset(),
480                         availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
481                         defaultWidgetPadding.right), searchBarSpaceHeightPx);
482             }
483         }
484         return bounds;
485     }
486 
487     /** Returns the bounds of the workspace page indicators. */
getWorkspacePageIndicatorBounds(Rect insets)488     Rect getWorkspacePageIndicatorBounds(Rect insets) {
489         Rect workspacePadding = getWorkspacePadding();
490         if (isLandscape && transposeLayoutWithOrientation) {
491             if (isLayoutRtl) {
492                 return new Rect(workspacePadding.left, workspacePadding.top,
493                         workspacePadding.left + pageIndicatorHeightPx,
494                         heightPx - workspacePadding.bottom - insets.bottom);
495             } else {
496                 int pageIndicatorLeft = widthPx - workspacePadding.right;
497                 return new Rect(pageIndicatorLeft, workspacePadding.top,
498                         pageIndicatorLeft + pageIndicatorHeightPx,
499                         heightPx - workspacePadding.bottom - insets.bottom);
500             }
501         } else {
502             int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom;
503             return new Rect(workspacePadding.left, pageIndicatorTop,
504                     widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx);
505         }
506     }
507 
508     /** Returns the workspace padding in the specified orientation */
getWorkspacePadding()509     Rect getWorkspacePadding() {
510         return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
511     }
getWorkspacePadding(int orientation)512     Rect getWorkspacePadding(int orientation) {
513         Rect searchBarBounds = getSearchBarBounds(orientation);
514         Rect padding = new Rect();
515         if (orientation == CellLayout.LANDSCAPE &&
516                 transposeLayoutWithOrientation) {
517             // Pad the left and right of the workspace with search/hotseat bar sizes
518             if (isLayoutRtl) {
519                 padding.set(hotseatBarHeightPx, edgeMarginPx,
520                         searchBarBounds.width(), edgeMarginPx);
521             } else {
522                 padding.set(searchBarBounds.width(), edgeMarginPx,
523                         hotseatBarHeightPx, edgeMarginPx);
524             }
525         } else {
526             if (isTablet()) {
527                 // Pad the left and right of the workspace to ensure consistent spacing
528                 // between all icons
529                 float gapScale = 1f + (dragViewScale - 1f) / 2f;
530                 int width = (orientation == CellLayout.LANDSCAPE)
531                         ? Math.max(widthPx, heightPx)
532                         : Math.min(widthPx, heightPx);
533                 int height = (orientation != CellLayout.LANDSCAPE)
534                         ? Math.max(widthPx, heightPx)
535                         : Math.min(widthPx, heightPx);
536                 int paddingTop = searchBarBounds.bottom;
537                 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
538                 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
539                         (numColumns * gapScale * cellWidthPx)));
540                 int availableHeight = Math.max(0, height - paddingTop - paddingBottom
541                         - (int) (2 * numRows * cellHeightPx));
542                 padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
543                         availableWidth / 2, paddingBottom + availableHeight / 2);
544             } else {
545                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
546                 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
547                         searchBarBounds.bottom,
548                         desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
549                         hotseatBarHeightPx + pageIndicatorHeightPx);
550             }
551         }
552         return padding;
553     }
554 
getWorkspacePageSpacing(int orientation)555     int getWorkspacePageSpacing(int orientation) {
556         if ((orientation == CellLayout.LANDSCAPE &&
557                 transposeLayoutWithOrientation) || isLargeTablet()) {
558             // In landscape mode the page spacing is set to the default.
559             return defaultPageSpacingPx;
560         } else {
561             // In portrait, we want the pages spaced such that there is no
562             // overhang of the previous / next page into the current page viewport.
563             // We assume symmetrical padding in portrait mode.
564             return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
565         }
566     }
567 
getOverviewModeButtonBarRect()568     Rect getOverviewModeButtonBarRect() {
569         int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
570         zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
571                 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
572         return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
573     }
574 
getOverviewModeScale()575     float getOverviewModeScale() {
576         Rect workspacePadding = getWorkspacePadding();
577         Rect overviewBar = getOverviewModeButtonBarRect();
578         int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
579         return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
580     }
581 
582     // The rect returned will be extended to below the system ui that covers the workspace
getHotseatRect()583     Rect getHotseatRect() {
584         if (isVerticalBarLayout()) {
585             return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
586                     Integer.MAX_VALUE, availableHeightPx);
587         } else {
588             return new Rect(0, availableHeightPx - hotseatBarHeightPx,
589                     availableWidthPx, Integer.MAX_VALUE);
590         }
591     }
592 
calculateCellWidth(int width, int countX)593     int calculateCellWidth(int width, int countX) {
594         return width / countX;
595     }
calculateCellHeight(int height, int countY)596     int calculateCellHeight(int height, int countY) {
597         return height / countY;
598     }
599 
isPhone()600     boolean isPhone() {
601         return !isTablet && !isLargeTablet;
602     }
isTablet()603     boolean isTablet() {
604         return isTablet;
605     }
isLargeTablet()606     boolean isLargeTablet() {
607         return isLargeTablet;
608     }
609 
isVerticalBarLayout()610     boolean isVerticalBarLayout() {
611         return isLandscape && transposeLayoutWithOrientation;
612     }
613 
shouldFadeAdjacentWorkspaceScreens()614     boolean shouldFadeAdjacentWorkspaceScreens() {
615         return isVerticalBarLayout() || isLargeTablet();
616     }
617 
getVisibleChildCount(ViewGroup parent)618     int getVisibleChildCount(ViewGroup parent) {
619         int visibleChildren = 0;
620         for (int i = 0; i < parent.getChildCount(); i++) {
621             if (parent.getChildAt(i).getVisibility() != View.GONE) {
622                 visibleChildren++;
623             }
624         }
625         return visibleChildren;
626     }
627 
calculateOverviewModeWidth(int visibleChildCount)628     int calculateOverviewModeWidth(int visibleChildCount) {
629         return visibleChildCount * overviewModeBarItemWidthPx +
630                 (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
631     }
632 
layout(Launcher launcher)633     public void layout(Launcher launcher) {
634         FrameLayout.LayoutParams lp;
635         Resources res = launcher.getResources();
636         boolean hasVerticalBarLayout = isVerticalBarLayout();
637 
638         // Layout the search bar space
639         View searchBar = launcher.getSearchBar();
640         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
641         if (hasVerticalBarLayout) {
642             // Vertical search bar space
643             lp.gravity = Gravity.TOP | Gravity.LEFT;
644             lp.width = searchBarSpaceHeightPx;
645             lp.height = LayoutParams.WRAP_CONTENT;
646             searchBar.setPadding(
647                     0, 2 * edgeMarginPx, 0,
648                     2 * edgeMarginPx);
649 
650             LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
651             targets.setOrientation(LinearLayout.VERTICAL);
652         } else {
653             // Horizontal search bar space
654             lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
655             lp.width = searchBarSpaceWidthPx;
656             lp.height = searchBarSpaceHeightPx;
657             searchBar.setPadding(
658                     2 * edgeMarginPx,
659                     getSearchBarTopOffset(),
660                     2 * edgeMarginPx, 0);
661         }
662         searchBar.setLayoutParams(lp);
663 
664         // Layout the voice proxy
665         View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
666         if (voiceButtonProxy != null) {
667             if (hasVerticalBarLayout) {
668                 // TODO: MOVE THIS INTO SEARCH BAR MEASURE
669             } else {
670                 lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
671                 lp.gravity = Gravity.TOP | Gravity.END;
672                 lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
673                         2 * iconSizePx;
674                 lp.height = searchBarSpaceHeightPx;
675             }
676         }
677 
678         // Layout the workspace
679         PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
680         lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
681         lp.gravity = Gravity.CENTER;
682         int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
683         Rect padding = getWorkspacePadding(orientation);
684         workspace.setLayoutParams(lp);
685         workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
686         workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
687 
688         // Layout the hotseat
689         View hotseat = launcher.findViewById(R.id.hotseat);
690         lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
691         if (hasVerticalBarLayout) {
692             // Vertical hotseat
693             lp.gravity = Gravity.END;
694             lp.width = hotseatBarHeightPx;
695             lp.height = LayoutParams.MATCH_PARENT;
696             hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
697         } else if (isTablet()) {
698             // Pad the hotseat with the workspace padding calculated above
699             lp.gravity = Gravity.BOTTOM;
700             lp.width = LayoutParams.MATCH_PARENT;
701             lp.height = hotseatBarHeightPx;
702             hotseat.setPadding(edgeMarginPx + padding.left, 0,
703                     edgeMarginPx + padding.right,
704                     2 * edgeMarginPx);
705         } else {
706             // For phones, layout the hotseat without any bottom margin
707             // to ensure that we have space for the folders
708             lp.gravity = Gravity.BOTTOM;
709             lp.width = LayoutParams.MATCH_PARENT;
710             lp.height = hotseatBarHeightPx;
711             hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
712                     2 * edgeMarginPx, 0);
713         }
714         hotseat.setLayoutParams(lp);
715 
716         // Layout the page indicators
717         View pageIndicator = launcher.findViewById(R.id.page_indicator);
718         if (pageIndicator != null) {
719             if (hasVerticalBarLayout) {
720                 // Hide the page indicators when we have vertical search/hotseat
721                 pageIndicator.setVisibility(View.GONE);
722             } else {
723                 // Put the page indicators above the hotseat
724                 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
725                 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
726                 lp.width = LayoutParams.WRAP_CONTENT;
727                 lp.height = LayoutParams.WRAP_CONTENT;
728                 lp.bottomMargin = hotseatBarHeightPx;
729                 pageIndicator.setLayoutParams(lp);
730             }
731         }
732 
733         // Layout AllApps
734         AppsCustomizeTabHost host = (AppsCustomizeTabHost)
735                 launcher.findViewById(R.id.apps_customize_pane);
736         if (host != null) {
737             // Center the all apps page indicator
738             int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
739                     (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
740             pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
741             if (pageIndicator != null) {
742                 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
743                 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
744                 lp.width = LayoutParams.WRAP_CONTENT;
745                 lp.height = pageIndicatorHeight;
746                 pageIndicator.setLayoutParams(lp);
747             }
748 
749             AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
750                     host.findViewById(R.id.apps_customize_pane_content);
751             padding = new Rect();
752             if (pagedView != null) {
753                 // Constrain the dimensions of all apps so that it does not span the full width
754                 int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
755                         (2 * (allAppsNumCols + 1));
756                 int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
757                         (2 * (allAppsNumRows + 1));
758                 paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
759                 paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
760                 int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
761                 int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
762                 // Only adjust the side paddings on landscape phones, or tablets
763                 if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
764                     padding.left = padding.right = gridPaddingLR;
765                 }
766                 // The icons are centered, so we can't just offset by the page indicator height
767                 // because the empty space will actually be pageIndicatorHeight + paddingTB
768                 padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
769                 pagedView.setAllAppsPadding(padding);
770                 pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
771             }
772         }
773 
774         // Layout the Overview Mode
775         ViewGroup overviewMode = launcher.getOverviewPanel();
776         if (overviewMode != null) {
777             Rect r = getOverviewModeButtonBarRect();
778             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
779             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
780             lp.width = Math.min(availableWidthPx,
781                     calculateOverviewModeWidth(getVisibleChildCount(overviewMode)));
782             lp.height = r.height();
783             overviewMode.setLayoutParams(lp);
784         }
785     }
786 }
787