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.Resources;
23 import android.graphics.Paint;
24 import android.graphics.Paint.FontMetrics;
25 import android.graphics.Point;
26 import android.graphics.Rect;
27 import android.util.DisplayMetrics;
28 import android.view.Gravity;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.ViewGroup.LayoutParams;
32 import android.view.ViewGroup.MarginLayoutParams;
33 import android.widget.FrameLayout;
34 import android.widget.LinearLayout;
35 
36 public class DeviceProfile {
37 
38     public final InvariantDeviceProfile inv;
39 
40     // Device properties
41     public final boolean isTablet;
42     public final boolean isLargeTablet;
43     public final boolean isPhone;
44     public final boolean transposeLayoutWithOrientation;
45 
46     // Device properties in current orientation
47     public final boolean isLandscape;
48     public final int widthPx;
49     public final int heightPx;
50     public final int availableWidthPx;
51     public final int availableHeightPx;
52     /**
53      * The maximum amount of left/right workspace padding as a percentage of the screen width.
54      * To be clear, this means that up to 7% of the screen width can be used as left padding, and
55      * 7% of the screen width can be used as right padding.
56      */
57     private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f;
58 
59     // Overview mode
60     private final int overviewModeMinIconZoneHeightPx;
61     private final int overviewModeMaxIconZoneHeightPx;
62     private final int overviewModeBarItemWidthPx;
63     private final int overviewModeBarSpacerWidthPx;
64     private final float overviewModeIconZoneRatio;
65 
66     // Workspace
67     private int desiredWorkspaceLeftRightMarginPx;
68     public final int edgeMarginPx;
69     public final Rect defaultWidgetPadding;
70     private final int pageIndicatorHeightPx;
71     private final int defaultPageSpacingPx;
72     private float dragViewScale;
73 
74     // Workspace icons
75     public int iconSizePx;
76     public int iconTextSizePx;
77     public int iconDrawablePaddingPx;
78     public int iconDrawablePaddingOriginalPx;
79 
80     public int cellWidthPx;
81     public int cellHeightPx;
82 
83     // Folder
84     public int folderBackgroundOffset;
85     public int folderIconSizePx;
86     public int folderCellWidthPx;
87     public int folderCellHeightPx;
88 
89     // Hotseat
90     public int hotseatCellWidthPx;
91     public int hotseatCellHeightPx;
92     public int hotseatIconSizePx;
93     private int normalHotseatBarHeightPx, shortHotseatBarHeightPx;
94     private int hotseatBarHeightPx; // One of the above.
95 
96     // All apps
97     public int allAppsNumCols;
98     public int allAppsNumPredictiveCols;
99     public int allAppsButtonVisualSize;
100     public final int allAppsIconSizePx;
101     public final float allAppsIconTextSizeSp;
102 
103     // QSB
104     private int searchBarWidgetInternalPaddingTop, searchBarWidgetInternalPaddingBottom;
105     private int searchBarTopPaddingPx;
106     private int tallSearchBarNegativeTopPaddingPx, normalSearchBarTopExtraPaddingPx;
107     private int searchBarTopExtraPaddingPx; // One of the above.
108     private int normalSearchBarBottomPaddingPx, tallSearchBarBottomPaddingPx;
109     private int searchBarBottomPaddingPx; // One of the above.
110     private int normalSearchBarSpaceHeightPx, tallSearchBarSpaceHeightPx;
111     private int searchBarSpaceHeightPx; // One of the above.
112 
DeviceProfile(Context context, InvariantDeviceProfile inv, Point minSize, Point maxSize, int width, int height, boolean isLandscape)113     public DeviceProfile(Context context, InvariantDeviceProfile inv,
114             Point minSize, Point maxSize,
115             int width, int height, boolean isLandscape) {
116 
117         this.inv = inv;
118         this.isLandscape = isLandscape;
119 
120         Resources res = context.getResources();
121         DisplayMetrics dm = res.getDisplayMetrics();
122 
123         // Constants from resources
124         isTablet = res.getBoolean(R.bool.is_tablet);
125         isLargeTablet = res.getBoolean(R.bool.is_large_tablet);
126         isPhone = !isTablet && !isLargeTablet;
127 
128         // Some more constants
129         transposeLayoutWithOrientation =
130                 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
131 
132         ComponentName cn = new ComponentName(context.getPackageName(),
133                 this.getClass().getName());
134         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
135         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
136         desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
137         pageIndicatorHeightPx =
138                 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
139         defaultPageSpacingPx =
140                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
141         overviewModeMinIconZoneHeightPx =
142                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
143         overviewModeMaxIconZoneHeightPx =
144                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
145         overviewModeBarItemWidthPx =
146                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
147         overviewModeBarSpacerWidthPx =
148                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
149         overviewModeIconZoneRatio =
150                 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
151         iconDrawablePaddingOriginalPx =
152                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
153 
154         // AllApps uses the original non-scaled icon text size
155         allAppsIconTextSizeSp = inv.iconTextSize;
156 
157         // AllApps uses the original non-scaled icon size
158         allAppsIconSizePx = Utilities.pxFromDp(inv.iconSize, dm);
159 
160         // Determine sizes.
161         widthPx = width;
162         heightPx = height;
163         if (isLandscape) {
164             availableWidthPx = maxSize.x;
165             availableHeightPx = minSize.y;
166         } else {
167             availableWidthPx = minSize.x;
168             availableHeightPx = maxSize.y;
169         }
170 
171         // Calculate the remaining vars
172         updateAvailableDimensions(dm, res);
173         computeAllAppsButtonSize(context);
174     }
175 
176     /**
177      * Determine the exact visual footprint of the all apps button, taking into account scaling
178      * and internal padding of the drawable.
179      */
computeAllAppsButtonSize(Context context)180     private void computeAllAppsButtonSize(Context context) {
181         Resources res = context.getResources();
182         float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
183         allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding)) - context.getResources()
184                         .getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
185     }
186 
updateAvailableDimensions(DisplayMetrics dm, Resources res)187     private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
188         // Check to see if the icons fit in the new available height.  If not, then we need to
189         // shrink the icon size.
190         float scale = 1f;
191         int drawablePadding = iconDrawablePaddingOriginalPx;
192         updateIconSize(1f, drawablePadding, res, dm);
193         float usedHeight = (cellHeightPx * inv.numRows);
194 
195         // We only care about the top and bottom workspace padding, which is not affected by RTL.
196         Rect workspacePadding = getWorkspacePadding(false /* isLayoutRtl */);
197         int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
198         if (usedHeight > maxHeight) {
199             scale = maxHeight / usedHeight;
200             drawablePadding = 0;
201         }
202         updateIconSize(scale, drawablePadding, res, dm);
203     }
204 
updateIconSize(float scale, int drawablePadding, Resources res, DisplayMetrics dm)205     private void updateIconSize(float scale, int drawablePadding, Resources res,
206                                 DisplayMetrics dm) {
207         iconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale);
208         iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
209         iconDrawablePaddingPx = drawablePadding;
210         hotseatIconSizePx = (int) (Utilities.pxFromDp(inv.hotseatIconSize, dm) * scale);
211 
212         // Search Bar
213         normalSearchBarSpaceHeightPx = res.getDimensionPixelSize(
214                 R.dimen.dynamic_grid_search_bar_height);
215         tallSearchBarSpaceHeightPx = res.getDimensionPixelSize(
216                 R.dimen.dynamic_grid_search_bar_height_tall);
217         searchBarWidgetInternalPaddingTop = res.getDimensionPixelSize(
218                 R.dimen.qsb_internal_padding_top);
219         searchBarWidgetInternalPaddingBottom = res.getDimensionPixelSize(
220                 R.dimen.qsb_internal_padding_bottom);
221         normalSearchBarTopExtraPaddingPx = res.getDimensionPixelSize(
222                 R.dimen.dynamic_grid_search_bar_extra_top_padding);
223         tallSearchBarNegativeTopPaddingPx = res.getDimensionPixelSize(
224                 R.dimen.dynamic_grid_search_bar_negative_top_padding_short);
225         if (isTablet && !isVerticalBarLayout()) {
226             searchBarTopPaddingPx = searchBarWidgetInternalPaddingTop;
227             normalSearchBarBottomPaddingPx = searchBarWidgetInternalPaddingBottom +
228                     res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_bottom_padding_tablet);
229             tallSearchBarBottomPaddingPx = normalSearchBarBottomPaddingPx;
230         } else {
231             searchBarTopPaddingPx = searchBarWidgetInternalPaddingTop;
232             normalSearchBarBottomPaddingPx = searchBarWidgetInternalPaddingBottom +
233                     res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_bottom_padding);
234             tallSearchBarBottomPaddingPx = searchBarWidgetInternalPaddingBottom
235                     + res.getDimensionPixelSize(
236                     R.dimen.dynamic_grid_search_bar_bottom_negative_padding_short);
237         }
238 
239         // Calculate the actual text height
240         Paint textPaint = new Paint();
241         textPaint.setTextSize(iconTextSizePx);
242         FontMetrics fm = textPaint.getFontMetrics();
243         cellWidthPx = iconSizePx;
244         cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
245         final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
246         dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
247 
248         // Hotseat
249         normalHotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
250         shortHotseatBarHeightPx = iconSizePx + 2 * edgeMarginPx;
251         hotseatCellWidthPx = iconSizePx;
252         hotseatCellHeightPx = iconSizePx;
253 
254         // Folder
255         int folderCellPadding = isTablet || isLandscape ? 6 * edgeMarginPx : 3 * edgeMarginPx;
256         // Don't let the folder get too close to the edges of the screen.
257         folderCellWidthPx = Math.min(cellWidthPx + folderCellPadding,
258                 (availableWidthPx - 4 * edgeMarginPx) / inv.numFolderColumns);
259         folderCellHeightPx = cellHeightPx + edgeMarginPx;
260         folderBackgroundOffset = -edgeMarginPx;
261         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
262     }
263 
264     /**
265      * @param recyclerViewWidth the available width of the AllAppsRecyclerView
266      */
updateAppsViewNumCols(Resources res, int recyclerViewWidth)267     public void updateAppsViewNumCols(Resources res, int recyclerViewWidth) {
268         int appsViewLeftMarginPx =
269                 res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
270         int allAppsCellWidthGap =
271                 res.getDimensionPixelSize(R.dimen.all_apps_icon_width_gap);
272         int availableAppsWidthPx = (recyclerViewWidth > 0) ? recyclerViewWidth : availableWidthPx;
273         int numAppsCols = (availableAppsWidthPx + allAppsCellWidthGap - appsViewLeftMarginPx) /
274                 (allAppsIconSizePx + allAppsCellWidthGap);
275         int numPredictiveAppCols = Math.max(inv.minAllAppsPredictionColumns, numAppsCols);
276         allAppsNumCols = numAppsCols;
277         allAppsNumPredictiveCols = numPredictiveAppCols;
278     }
279 
280     /** Returns the amount of extra space to allocate to the search bar for vertical padding. */
getSearchBarTotalVerticalPadding()281     private int getSearchBarTotalVerticalPadding() {
282         return searchBarTopPaddingPx + searchBarTopExtraPaddingPx + searchBarBottomPaddingPx;
283     }
284 
285     /** Returns the width and height of the search bar, ignoring any padding. */
getSearchBarDimensForWidgetOpts(Resources res)286     public Point getSearchBarDimensForWidgetOpts(Resources res) {
287         Rect searchBarBounds = getSearchBarBounds(Utilities.isRtl(res));
288         if (isVerticalBarLayout()) {
289             return new Point(searchBarBounds.width(), searchBarBounds.height());
290         }
291         int widgetInternalPadding = searchBarWidgetInternalPaddingTop +
292                 searchBarWidgetInternalPaddingBottom;
293         return new Point(searchBarBounds.width(), searchBarSpaceHeightPx + widgetInternalPadding);
294     }
295 
296     /** Returns the search bar bounds in the current orientation */
getSearchBarBounds(boolean isLayoutRtl)297     public Rect getSearchBarBounds(boolean isLayoutRtl) {
298         Rect bounds = new Rect();
299         if (isVerticalBarLayout()) {
300             if (isLayoutRtl) {
301                 bounds.set(availableWidthPx - normalSearchBarSpaceHeightPx, edgeMarginPx,
302                         availableWidthPx, availableHeightPx - edgeMarginPx);
303             } else {
304                 bounds.set(0, edgeMarginPx, normalSearchBarSpaceHeightPx,
305                         availableHeightPx - edgeMarginPx);
306             }
307         } else {
308             int boundsBottom = searchBarSpaceHeightPx + getSearchBarTotalVerticalPadding();
309             if (isTablet) {
310                 // Pad the left and right of the workspace to ensure consistent spacing
311                 // between all icons
312                 int width = getCurrentWidth();
313                 // XXX: If the icon size changes across orientations, we will have to take
314                 //      that into account here too.
315                 int gap = (int) ((width - 2 * edgeMarginPx -
316                         (inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1)));
317                 bounds.set(edgeMarginPx + gap, 0,
318                         availableWidthPx - (edgeMarginPx + gap), boundsBottom);
319             } else {
320                 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
321                         0,
322                         availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
323                         defaultWidgetPadding.right), boundsBottom);
324             }
325         }
326         return bounds;
327     }
328 
329     /** Returns the workspace padding in the specified orientation */
getWorkspacePadding(boolean isLayoutRtl)330     Rect getWorkspacePadding(boolean isLayoutRtl) {
331         Rect searchBarBounds = getSearchBarBounds(isLayoutRtl);
332         Rect padding = new Rect();
333         if (isVerticalBarLayout()) {
334             // Pad the left and right of the workspace with search/hotseat bar sizes
335             if (isLayoutRtl) {
336                 padding.set(normalHotseatBarHeightPx, edgeMarginPx,
337                         searchBarBounds.width(), edgeMarginPx);
338             } else {
339                 padding.set(searchBarBounds.width(), edgeMarginPx,
340                         normalHotseatBarHeightPx, edgeMarginPx);
341             }
342         } else {
343             int paddingTop = searchBarBounds.bottom;
344             int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
345             if (isTablet) {
346                 // Pad the left and right of the workspace to ensure consistent spacing
347                 // between all icons
348                 float gapScale = 1f + (dragViewScale - 1f) / 2f;
349                 int width = getCurrentWidth();
350                 int height = getCurrentHeight();
351                 // The amount of screen space available for left/right padding.
352                 int availablePaddingX = Math.max(0, width - (int) ((inv.numColumns * cellWidthPx) +
353                         ((inv.numColumns - 1) * gapScale * cellWidthPx)));
354                 availablePaddingX = (int) Math.min(availablePaddingX,
355                             width * MAX_HORIZONTAL_PADDING_PERCENT);
356                 int availablePaddingY = Math.max(0, height - paddingTop - paddingBottom
357                         - (int) (2 * inv.numRows * cellHeightPx));
358                 padding.set(availablePaddingX / 2, paddingTop + availablePaddingY / 2,
359                         availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
360             } else {
361                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
362                 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
363                         paddingTop,
364                         desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
365                         paddingBottom);
366             }
367         }
368         return padding;
369     }
370 
getWorkspacePageSpacing(boolean isLayoutRtl)371     private int getWorkspacePageSpacing(boolean isLayoutRtl) {
372         if (isVerticalBarLayout() || isLargeTablet) {
373             // In landscape mode the page spacing is set to the default.
374             return defaultPageSpacingPx;
375         } else {
376             // In portrait, we want the pages spaced such that there is no
377             // overhang of the previous / next page into the current page viewport.
378             // We assume symmetrical padding in portrait mode.
379             return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding(isLayoutRtl).left);
380         }
381     }
382 
getOverviewModeButtonBarHeight()383     int getOverviewModeButtonBarHeight() {
384         int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
385         zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
386                 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
387         return zoneHeight;
388     }
389 
390     // The rect returned will be extended to below the system ui that covers the workspace
getHotseatRect()391     Rect getHotseatRect() {
392         if (isVerticalBarLayout()) {
393             return new Rect(availableWidthPx - normalHotseatBarHeightPx, 0,
394                     Integer.MAX_VALUE, availableHeightPx);
395         } else {
396             return new Rect(0, availableHeightPx - hotseatBarHeightPx,
397                     availableWidthPx, Integer.MAX_VALUE);
398         }
399     }
400 
calculateCellWidth(int width, int countX)401     public static int calculateCellWidth(int width, int countX) {
402         return width / countX;
403     }
calculateCellHeight(int height, int countY)404     public static int calculateCellHeight(int height, int countY) {
405         return height / countY;
406     }
407 
408     /**
409      * When {@code true}, the device is in landscape mode and the hotseat is on the right column.
410      * When {@code false}, either device is in portrait mode or the device is in landscape mode and
411      * the hotseat is on the bottom row.
412      */
isVerticalBarLayout()413     boolean isVerticalBarLayout() {
414         return isLandscape && transposeLayoutWithOrientation;
415     }
416 
shouldFadeAdjacentWorkspaceScreens()417     boolean shouldFadeAdjacentWorkspaceScreens() {
418         return isVerticalBarLayout() || isLargeTablet;
419     }
420 
getVisibleChildCount(ViewGroup parent)421     private int getVisibleChildCount(ViewGroup parent) {
422         int visibleChildren = 0;
423         for (int i = 0; i < parent.getChildCount(); i++) {
424             if (parent.getChildAt(i).getVisibility() != View.GONE) {
425                 visibleChildren++;
426             }
427         }
428         return visibleChildren;
429     }
430 
431     // TODO(twickham): b/25154513
setSearchBarHeight(int searchBarHeight)432     public void setSearchBarHeight(int searchBarHeight) {
433         if (searchBarHeight == LauncherCallbacks.SEARCH_BAR_HEIGHT_TALL) {
434             hotseatBarHeightPx = shortHotseatBarHeightPx;
435             searchBarSpaceHeightPx = tallSearchBarSpaceHeightPx;
436             searchBarBottomPaddingPx = tallSearchBarBottomPaddingPx;
437             searchBarTopExtraPaddingPx = isPhone ? tallSearchBarNegativeTopPaddingPx
438                     : normalSearchBarTopExtraPaddingPx;
439         } else {
440             hotseatBarHeightPx = normalHotseatBarHeightPx;
441             searchBarSpaceHeightPx = normalSearchBarSpaceHeightPx;
442             searchBarBottomPaddingPx = normalSearchBarBottomPaddingPx;
443             searchBarTopExtraPaddingPx = normalSearchBarTopExtraPaddingPx;
444         }
445     }
446 
layout(Launcher launcher)447     public void layout(Launcher launcher) {
448         FrameLayout.LayoutParams lp;
449         boolean hasVerticalBarLayout = isVerticalBarLayout();
450         final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
451 
452         // Layout the search bar space
453         Rect searchBarBounds = getSearchBarBounds(isLayoutRtl);
454         View searchBar = launcher.getSearchDropTargetBar();
455         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
456         lp.width = searchBarBounds.width();
457         lp.height = searchBarBounds.height();
458         lp.topMargin = searchBarTopExtraPaddingPx;
459         if (hasVerticalBarLayout) {
460             // Vertical search bar space -- The search bar is fixed in the layout to be on the left
461             //                              of the screen regardless of RTL
462             lp.gravity = Gravity.LEFT;
463 
464             LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
465             targets.setOrientation(LinearLayout.VERTICAL);
466             FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams();
467             targetsLp.gravity = Gravity.TOP;
468             targetsLp.height = LayoutParams.WRAP_CONTENT;
469 
470         } else {
471             // Horizontal search bar space
472             lp.gravity = Gravity.TOP|Gravity.CENTER_HORIZONTAL;
473         }
474         searchBar.setLayoutParams(lp);
475 
476         // Layout the workspace
477         PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
478         lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
479         lp.gravity = Gravity.CENTER;
480         Rect padding = getWorkspacePadding(isLayoutRtl);
481         workspace.setLayoutParams(lp);
482         workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
483         workspace.setPageSpacing(getWorkspacePageSpacing(isLayoutRtl));
484 
485         // Layout the hotseat
486         View hotseat = launcher.findViewById(R.id.hotseat);
487         lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
488         // We want the edges of the hotseat to line up with the edges of the workspace, but the
489         // icons in the hotseat are a different size, and so don't line up perfectly. To account for
490         // this, we pad the left and right of the hotseat with half of the difference of a workspace
491         // cell vs a hotseat cell.
492         float workspaceCellWidth = (float) getCurrentWidth() / inv.numColumns;
493         float hotseatCellWidth = (float) getCurrentWidth() / inv.numHotseatIcons;
494         int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
495         if (hasVerticalBarLayout) {
496             // Vertical hotseat -- The hotseat is fixed in the layout to be on the right of the
497             //                     screen regardless of RTL
498             lp.gravity = Gravity.RIGHT;
499             lp.width = normalHotseatBarHeightPx;
500             lp.height = LayoutParams.MATCH_PARENT;
501             hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
502         } else if (isTablet) {
503             // Pad the hotseat with the workspace padding calculated above
504             lp.gravity = Gravity.BOTTOM;
505             lp.width = LayoutParams.MATCH_PARENT;
506             lp.height = hotseatBarHeightPx;
507             hotseat.findViewById(R.id.layout).setPadding(
508                     hotseatAdjustment + padding.left, 0,
509                     hotseatAdjustment + padding.right, 2 * edgeMarginPx);
510         } else {
511             // For phones, layout the hotseat without any bottom margin
512             // to ensure that we have space for the folders
513             lp.gravity = Gravity.BOTTOM;
514             lp.width = LayoutParams.MATCH_PARENT;
515             lp.height = hotseatBarHeightPx;
516             hotseat.findViewById(R.id.layout).setPadding(
517                     hotseatAdjustment + padding.left, 0,
518                     hotseatAdjustment + padding.right, 0);
519         }
520         hotseat.setLayoutParams(lp);
521 
522         // Layout the page indicators
523         View pageIndicator = launcher.findViewById(R.id.page_indicator);
524         if (pageIndicator != null) {
525             if (hasVerticalBarLayout) {
526                 // Hide the page indicators when we have vertical search/hotseat
527                 pageIndicator.setVisibility(View.GONE);
528             } else {
529                 // Put the page indicators above the hotseat
530                 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
531                 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
532                 lp.width = LayoutParams.WRAP_CONTENT;
533                 lp.height = LayoutParams.WRAP_CONTENT;
534                 lp.bottomMargin = hotseatBarHeightPx;
535                 pageIndicator.setLayoutParams(lp);
536             }
537         }
538 
539         // Layout the Overview Mode
540         ViewGroup overviewMode = launcher.getOverviewPanel();
541         if (overviewMode != null) {
542             int overviewButtonBarHeight = getOverviewModeButtonBarHeight();
543             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
544             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
545 
546             int visibleChildCount = getVisibleChildCount(overviewMode);
547             int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
548             int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
549 
550             lp.width = Math.min(availableWidthPx, maxWidth);
551             lp.height = overviewButtonBarHeight;
552             overviewMode.setLayoutParams(lp);
553 
554             if (lp.width > totalItemWidth && visibleChildCount > 1) {
555                 // We have enough space. Lets add some margin too.
556                 int margin = (lp.width - totalItemWidth) / (visibleChildCount-1);
557                 View lastChild = null;
558 
559                 // Set margin of all visible children except the last visible child
560                 for (int i = 0; i < visibleChildCount; i++) {
561                     if (lastChild != null) {
562                         MarginLayoutParams clp = (MarginLayoutParams) lastChild.getLayoutParams();
563                         if (isLayoutRtl) {
564                             clp.leftMargin = margin;
565                         } else {
566                             clp.rightMargin = margin;
567                         }
568                         lastChild.setLayoutParams(clp);
569                         lastChild = null;
570                     }
571                     View thisChild = overviewMode.getChildAt(i);
572                     if (thisChild.getVisibility() != View.GONE) {
573                         lastChild = thisChild;
574                     }
575                 }
576             }
577         }
578     }
579 
getCurrentWidth()580     private int getCurrentWidth() {
581         return isLandscape
582                 ? Math.max(widthPx, heightPx)
583                 : Math.min(widthPx, heightPx);
584     }
585 
getCurrentHeight()586     private int getCurrentHeight() {
587         return isLandscape
588                 ? Math.min(widthPx, heightPx)
589                 : Math.max(widthPx, heightPx);
590     }
591 }
592