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.Point;
24 import android.graphics.PointF;
25 import android.graphics.Rect;
26 import android.util.DisplayMetrics;
27 import android.view.Gravity;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.ViewGroup.LayoutParams;
31 import android.widget.FrameLayout;
32 
33 import com.android.launcher3.CellLayout.ContainerType;
34 import com.android.launcher3.badge.BadgeRenderer;
35 import com.android.launcher3.config.FeatureFlags;
36 
37 import java.util.ArrayList;
38 
39 public class DeviceProfile {
40 
41     public interface LauncherLayoutChangeListener {
onLauncherLayoutChanged()42         void onLauncherLayoutChanged();
43     }
44 
45     public final InvariantDeviceProfile inv;
46 
47     // Device properties
48     public final boolean isTablet;
49     public final boolean isLargeTablet;
50     public final boolean isPhone;
51     public final boolean transposeLayoutWithOrientation;
52 
53     // Device properties in current orientation
54     public final boolean isLandscape;
55     public final int widthPx;
56     public final int heightPx;
57     public final int availableWidthPx;
58     public final int availableHeightPx;
59     /**
60      * The maximum amount of left/right workspace padding as a percentage of the screen width.
61      * To be clear, this means that up to 7% of the screen width can be used as left padding, and
62      * 7% of the screen width can be used as right padding.
63      */
64     private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f;
65 
66     // Overview mode
67     private final int overviewModeMinIconZoneHeightPx;
68     private final int overviewModeMaxIconZoneHeightPx;
69     private final int overviewModeBarItemWidthPx;
70     private final int overviewModeBarSpacerWidthPx;
71     private final float overviewModeIconZoneRatio;
72 
73     // Workspace
74     private int desiredWorkspaceLeftRightMarginPx;
75     public final int edgeMarginPx;
76     public final Rect defaultWidgetPadding;
77     private final int defaultPageSpacingPx;
78     private final int topWorkspacePadding;
79     public float workspaceSpringLoadShrinkFactor;
80     public final int workspaceSpringLoadedBottomSpace;
81 
82     // Page indicator
83     private final int pageIndicatorHeightPx;
84     private final int pageIndicatorLandGutterLeftNavBarPx;
85     private final int pageIndicatorLandGutterRightNavBarPx;
86     private final int pageIndicatorLandWorkspaceOffsetPx;
87 
88     // Workspace icons
89     public int iconSizePx;
90     public int iconTextSizePx;
91     public int iconDrawablePaddingPx;
92     public int iconDrawablePaddingOriginalPx;
93 
94     public int cellWidthPx;
95     public int cellHeightPx;
96 
97     // Folder
98     public int folderBackgroundOffset;
99     public int folderIconSizePx;
100     public int folderIconPreviewPadding;
101 
102     // Folder cell
103     public int folderCellWidthPx;
104     public int folderCellHeightPx;
105 
106     // Folder child
107     public int folderChildIconSizePx;
108     public int folderChildTextSizePx;
109     public int folderChildDrawablePaddingPx;
110 
111     // Hotseat
112     public int hotseatCellWidthPx;
113     public int hotseatCellHeightPx;
114     public int hotseatIconSizePx;
115     public int hotseatBarHeightPx;
116     private int hotseatBarTopPaddingPx;
117     private int hotseatBarBottomPaddingPx;
118     private int hotseatLandGutterPx;
119 
120     // All apps
121     public int allAppsNumCols;
122     public int allAppsNumPredictiveCols;
123     public int allAppsButtonVisualSize;
124     public int allAppsIconSizePx;
125     public int allAppsIconDrawablePaddingPx;
126     public float allAppsIconTextSizePx;
127 
128     // Widgets
129     public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
130 
131     // Drop Target
132     public int dropTargetBarSizePx;
133 
134     // Insets
135     private Rect mInsets = new Rect();
136 
137     // Listeners
138     private ArrayList<LauncherLayoutChangeListener> mListeners = new ArrayList<>();
139 
140     // Icon badges
141     public BadgeRenderer mBadgeRenderer;
142 
DeviceProfile(Context context, InvariantDeviceProfile inv, Point minSize, Point maxSize, int width, int height, boolean isLandscape)143     public DeviceProfile(Context context, InvariantDeviceProfile inv,
144             Point minSize, Point maxSize,
145             int width, int height, boolean isLandscape) {
146 
147         this.inv = inv;
148         this.isLandscape = isLandscape;
149 
150         Resources res = context.getResources();
151         DisplayMetrics dm = res.getDisplayMetrics();
152 
153         // Constants from resources
154         isTablet = res.getBoolean(R.bool.is_tablet);
155         isLargeTablet = res.getBoolean(R.bool.is_large_tablet);
156         isPhone = !isTablet && !isLargeTablet;
157 
158         // Some more constants
159         transposeLayoutWithOrientation =
160                 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
161 
162         ComponentName cn = new ComponentName(context.getPackageName(),
163                 this.getClass().getName());
164         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
165         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
166         desiredWorkspaceLeftRightMarginPx = edgeMarginPx;
167         pageIndicatorHeightPx =
168                 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
169         pageIndicatorLandGutterLeftNavBarPx = res.getDimensionPixelSize(
170                 R.dimen.dynamic_grid_page_indicator_gutter_width_left_nav_bar);
171         pageIndicatorLandWorkspaceOffsetPx =
172                 res.getDimensionPixelSize(R.dimen.all_apps_caret_workspace_offset);
173         pageIndicatorLandGutterRightNavBarPx = res.getDimensionPixelSize(
174                 R.dimen.dynamic_grid_page_indicator_gutter_width_right_nav_bar);
175         defaultPageSpacingPx =
176                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
177         topWorkspacePadding =
178                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_top_padding);
179         overviewModeMinIconZoneHeightPx =
180                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
181         overviewModeMaxIconZoneHeightPx =
182                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
183         overviewModeBarItemWidthPx =
184                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
185         overviewModeBarSpacerWidthPx =
186                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
187         overviewModeIconZoneRatio =
188                 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
189         iconDrawablePaddingOriginalPx =
190                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
191         dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
192         workspaceSpringLoadedBottomSpace =
193                 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
194         hotseatBarHeightPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_height);
195         hotseatBarTopPaddingPx =
196                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
197         hotseatBarBottomPaddingPx = 0;
198         hotseatLandGutterPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_gutter_width);
199 
200         // Determine sizes.
201         widthPx = width;
202         heightPx = height;
203         if (isLandscape) {
204             availableWidthPx = maxSize.x;
205             availableHeightPx = minSize.y;
206         } else {
207             availableWidthPx = minSize.x;
208             availableHeightPx = maxSize.y;
209         }
210 
211         // Calculate the remaining vars
212         updateAvailableDimensions(dm, res);
213         computeAllAppsButtonSize(context);
214 
215         // This is done last, after iconSizePx is calculated above.
216         mBadgeRenderer = new BadgeRenderer(context, iconSizePx);
217     }
218 
getMultiWindowProfile(Context context, Point mwSize)219     DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
220         // In multi-window mode, we can have widthPx = availableWidthPx
221         // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
222         // widthPx and heightPx values where it's needed.
223         DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
224                 isLandscape);
225 
226         // Hide labels on the workspace.
227         profile.iconTextSizePx = 0;
228         profile.cellHeightPx = profile.iconSizePx + profile.iconDrawablePaddingPx
229                 + Utilities.calculateTextHeight(profile.iconTextSizePx);
230 
231         // The nav bar is black so we add bottom padding to visually center hotseat icons.
232         profile.hotseatBarBottomPaddingPx = profile.hotseatBarTopPaddingPx;
233 
234         // We use these scales to measure and layout the widgets using their full invariant profile
235         // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
236         float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
237         float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
238         profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
239 
240         return profile;
241     }
242 
addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener)243     public void addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
244         if (!mListeners.contains(listener)) {
245             mListeners.add(listener);
246         }
247     }
248 
removeLauncherLayoutChangedListener(LauncherLayoutChangeListener listener)249     public void removeLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
250         if (mListeners.contains(listener)) {
251             mListeners.remove(listener);
252         }
253     }
254 
255     /**
256      * Determine the exact visual footprint of the all apps button, taking into account scaling
257      * and internal padding of the drawable.
258      */
computeAllAppsButtonSize(Context context)259     private void computeAllAppsButtonSize(Context context) {
260         Resources res = context.getResources();
261         float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
262         allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding)) - context.getResources()
263                         .getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
264     }
265 
updateAvailableDimensions(DisplayMetrics dm, Resources res)266     private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
267         updateIconSize(1f, iconDrawablePaddingOriginalPx, res, dm);
268 
269         // Check to see if the icons fit within the available height.  If not, then scale down.
270         float usedHeight = (cellHeightPx * inv.numRows);
271         int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
272         if (usedHeight > maxHeight) {
273             float scale = maxHeight / usedHeight;
274             updateIconSize(scale, 0, res, dm);
275         }
276 
277         updateAvailableFolderCellDimensions(dm, res);
278     }
279 
updateIconSize(float scale, int drawablePadding, Resources res, DisplayMetrics dm)280     private void updateIconSize(float scale, int drawablePadding, Resources res,
281                                 DisplayMetrics dm) {
282         iconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale);
283         iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
284         iconDrawablePaddingPx = drawablePadding;
285         hotseatIconSizePx = (int) (Utilities.pxFromDp(inv.hotseatIconSize, dm) * scale);
286         allAppsIconSizePx = iconSizePx;
287         allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
288         allAppsIconTextSizePx = iconTextSizePx;
289 
290         cellWidthPx = iconSizePx;
291         cellHeightPx = iconSizePx + iconDrawablePaddingPx
292                 + Utilities.calculateTextHeight(iconTextSizePx);
293 
294         // Hotseat
295         hotseatCellWidthPx = iconSizePx;
296         hotseatCellHeightPx = iconSizePx;
297 
298         if (!isVerticalBarLayout()) {
299             int expectedWorkspaceHeight = availableHeightPx - hotseatBarHeightPx
300                     - pageIndicatorHeightPx - topWorkspacePadding;
301             float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
302             workspaceSpringLoadShrinkFactor = Math.min(
303                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f,
304                     1 - (minRequiredHeight / expectedWorkspaceHeight));
305         } else {
306             workspaceSpringLoadShrinkFactor =
307                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
308         }
309 
310         // Folder icon
311         folderBackgroundOffset = -edgeMarginPx;
312         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
313         folderIconPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
314     }
315 
updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res)316     private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) {
317         int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
318                 + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
319                 + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
320 
321         updateFolderCellSize(1f, dm, res);
322 
323         // Don't let the folder get too close to the edges of the screen.
324         int folderMargin = 4 * edgeMarginPx;
325 
326         // Check if the icons fit within the available height.
327         float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize;
328         int maxHeight = availableHeightPx - getTotalWorkspacePadding().y - folderMargin;
329         float scaleY = maxHeight / usedHeight;
330 
331         // Check if the icons fit within the available width.
332         float usedWidth = folderCellWidthPx * inv.numFolderColumns;
333         int maxWidth = availableWidthPx - getTotalWorkspacePadding().x - folderMargin;
334         float scaleX = maxWidth / usedWidth;
335 
336         float scale = Math.min(scaleX, scaleY);
337         if (scale < 1f) {
338             updateFolderCellSize(scale, dm, res);
339         }
340     }
341 
updateFolderCellSize(float scale, DisplayMetrics dm, Resources res)342     private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) {
343         folderChildIconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale);
344         folderChildTextSizePx =
345                 (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
346 
347         int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
348         int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale);
349         int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale);
350 
351         folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
352         folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
353         folderChildDrawablePaddingPx = Math.max(0,
354                 (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
355     }
356 
updateInsets(Rect insets)357     public void updateInsets(Rect insets) {
358         mInsets.set(insets);
359     }
360 
updateAppsViewNumCols()361     public void updateAppsViewNumCols() {
362         allAppsNumCols = allAppsNumPredictiveCols = inv.numColumns;
363     }
364 
365     /** Returns the width and height of the search bar, ignoring any padding. */
getSearchBarDimensForWidgetOpts()366     public Point getSearchBarDimensForWidgetOpts() {
367         if (isVerticalBarLayout()) {
368             return new Point(dropTargetBarSizePx, availableHeightPx - 2 * edgeMarginPx);
369         } else {
370             int gap;
371             if (isTablet) {
372                 // Pad the left and right of the workspace to ensure consistent spacing
373                 // between all icons
374                 int width = getCurrentWidth();
375                 // XXX: If the icon size changes across orientations, we will have to take
376                 //      that into account here too.
377                 gap = ((width - 2 * edgeMarginPx
378                         - (inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1)))
379                         + edgeMarginPx;
380             } else {
381                 gap = desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right;
382             }
383             return new Point(availableWidthPx - 2 * gap, dropTargetBarSizePx);
384         }
385     }
386 
getCellSize()387     public Point getCellSize() {
388         Point result = new Point();
389         // Since we are only concerned with the overall padding, layout direction does
390         // not matter.
391         Point padding = getTotalWorkspacePadding();
392         result.x = calculateCellWidth(availableWidthPx - padding.x, inv.numColumns);
393         result.y = calculateCellHeight(availableHeightPx - padding.y, inv.numRows);
394         return result;
395     }
396 
getTotalWorkspacePadding()397     public Point getTotalWorkspacePadding() {
398         Rect padding = getWorkspacePadding(null);
399         return new Point(padding.left + padding.right, padding.top + padding.bottom);
400     }
401 
402     /**
403      * Returns the workspace padding in the specified orientation.
404      * Note that it assumes that while in verticalBarLayout, the nav bar is on the right, as such
405      * this value is not reliable.
406      * Use {@link #getTotalWorkspacePadding()} instead.
407      */
getWorkspacePadding(Rect recycle)408     public Rect getWorkspacePadding(Rect recycle) {
409         Rect padding = recycle == null ? new Rect() : recycle;
410         if (isVerticalBarLayout()) {
411             if (mInsets.left > 0) {
412                 padding.set(mInsets.left + pageIndicatorLandGutterLeftNavBarPx, 0,
413                         hotseatBarHeightPx + hotseatLandGutterPx - mInsets.left, 2 * edgeMarginPx);
414             } else {
415                 padding.set(pageIndicatorLandGutterRightNavBarPx, 0,
416                         hotseatBarHeightPx + hotseatLandGutterPx, 2 * edgeMarginPx);
417             }
418         } else {
419             int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
420             if (isTablet) {
421                 // Pad the left and right of the workspace to ensure consistent spacing
422                 // between all icons
423                 int width = getCurrentWidth();
424                 int height = getCurrentHeight();
425                 // The amount of screen space available for left/right padding.
426                 int availablePaddingX = Math.max(0, width - ((inv.numColumns * cellWidthPx) +
427                         ((inv.numColumns - 1) * cellWidthPx)));
428                 availablePaddingX = (int) Math.min(availablePaddingX,
429                             width * MAX_HORIZONTAL_PADDING_PERCENT);
430                 int availablePaddingY = Math.max(0, height - topWorkspacePadding - paddingBottom
431                         - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
432                         - hotseatBarBottomPaddingPx);
433                 padding.set(availablePaddingX / 2, topWorkspacePadding + availablePaddingY / 2,
434                         availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
435             } else {
436                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
437                 padding.set(desiredWorkspaceLeftRightMarginPx,
438                         topWorkspacePadding,
439                         desiredWorkspaceLeftRightMarginPx,
440                         paddingBottom);
441             }
442         }
443         return padding;
444     }
445 
446     /**
447      * @return the bounds for which the open folders should be contained within
448      */
getAbsoluteOpenFolderBounds()449     public Rect getAbsoluteOpenFolderBounds() {
450         if (isVerticalBarLayout()) {
451             // Folders should only appear right of the drop target bar and left of the hotseat
452             return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx,
453                     mInsets.top,
454                     mInsets.left + availableWidthPx - hotseatBarHeightPx - edgeMarginPx,
455                     mInsets.top + availableHeightPx);
456         } else {
457             // Folders should only appear below the drop target bar and above the hotseat
458             return new Rect(mInsets.left,
459                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
460                     mInsets.left + availableWidthPx,
461                     mInsets.top + availableHeightPx - hotseatBarHeightPx - pageIndicatorHeightPx -
462                             edgeMarginPx);
463         }
464     }
465 
getWorkspacePageSpacing()466     private int getWorkspacePageSpacing() {
467         if (isVerticalBarLayout() || isLargeTablet) {
468             // In landscape mode the page spacing is set to the default.
469             return defaultPageSpacingPx;
470         } else {
471             // In portrait, we want the pages spaced such that there is no
472             // overhang of the previous / next page into the current page viewport.
473             // We assume symmetrical padding in portrait mode.
474             return Math.max(defaultPageSpacingPx, getWorkspacePadding(null).left + 1);
475         }
476     }
477 
getOverviewModeButtonBarHeight()478     int getOverviewModeButtonBarHeight() {
479         int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
480         zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
481                 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
482         return zoneHeight;
483     }
484 
calculateCellWidth(int width, int countX)485     public static int calculateCellWidth(int width, int countX) {
486         return width / countX;
487     }
calculateCellHeight(int height, int countY)488     public static int calculateCellHeight(int height, int countY) {
489         return height / countY;
490     }
491 
492     /**
493      * When {@code true}, the device is in landscape mode and the hotseat is on the right column.
494      * When {@code false}, either device is in portrait mode or the device is in landscape mode and
495      * the hotseat is on the bottom row.
496      */
isVerticalBarLayout()497     public boolean isVerticalBarLayout() {
498         return isLandscape && transposeLayoutWithOrientation;
499     }
500 
shouldFadeAdjacentWorkspaceScreens()501     boolean shouldFadeAdjacentWorkspaceScreens() {
502         return isVerticalBarLayout() || isLargeTablet;
503     }
504 
getVisibleChildCount(ViewGroup parent)505     private int getVisibleChildCount(ViewGroup parent) {
506         int visibleChildren = 0;
507         for (int i = 0; i < parent.getChildCount(); i++) {
508             if (parent.getChildAt(i).getVisibility() != View.GONE) {
509                 visibleChildren++;
510             }
511         }
512         return visibleChildren;
513     }
514 
layout(Launcher launcher, boolean notifyListeners)515     public void layout(Launcher launcher, boolean notifyListeners) {
516         FrameLayout.LayoutParams lp;
517         boolean hasVerticalBarLayout = isVerticalBarLayout();
518 
519         // Layout the search bar space
520         Point searchBarBounds = getSearchBarDimensForWidgetOpts();
521         View searchBar = launcher.getDropTargetBar();
522         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
523         lp.width = searchBarBounds.x;
524         lp.height = searchBarBounds.y;
525         lp.topMargin = mInsets.top + edgeMarginPx;
526         searchBar.setLayoutParams(lp);
527 
528         // Layout the workspace
529         PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
530         Rect workspacePadding = getWorkspacePadding(null);
531         workspace.setPadding(workspacePadding.left, workspacePadding.top, workspacePadding.right,
532                 workspacePadding.bottom);
533         workspace.setPageSpacing(getWorkspacePageSpacing());
534 
535         // Only display when enabled
536         if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
537             View qsbContainer = launcher.getQsbContainer();
538             lp = (FrameLayout.LayoutParams) qsbContainer.getLayoutParams();
539             lp.topMargin = mInsets.top + workspacePadding.top;
540             qsbContainer.setLayoutParams(lp);
541         }
542 
543         // Layout the hotseat
544         Hotseat hotseat = (Hotseat) launcher.findViewById(R.id.hotseat);
545         lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
546         // We want the edges of the hotseat to line up with the edges of the workspace, but the
547         // icons in the hotseat are a different size, and so don't line up perfectly. To account for
548         // this, we pad the left and right of the hotseat with half of the difference of a workspace
549         // cell vs a hotseat cell.
550         float workspaceCellWidth = (float) getCurrentWidth() / inv.numColumns;
551         float hotseatCellWidth = (float) getCurrentWidth() / inv.numHotseatIcons;
552         int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
553         if (hasVerticalBarLayout) {
554             // Vertical hotseat -- The hotseat is fixed in the layout to be on the right of the
555             //                     screen regardless of RTL
556             lp.gravity = Gravity.RIGHT;
557             lp.width = hotseatBarHeightPx + mInsets.left + mInsets.right;
558             lp.height = LayoutParams.MATCH_PARENT;
559             hotseat.getLayout().setPadding(mInsets.left, mInsets.top, mInsets.right,
560                     workspacePadding.bottom);
561         } else if (isTablet) {
562             // Pad the hotseat with the workspace padding calculated above
563             lp.gravity = Gravity.BOTTOM;
564             lp.width = LayoutParams.MATCH_PARENT;
565             lp.height = hotseatBarHeightPx + mInsets.bottom;
566             hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left,
567                     hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right,
568                     hotseatBarBottomPaddingPx + mInsets.bottom);
569         } else {
570             // For phones, layout the hotseat without any bottom margin
571             // to ensure that we have space for the folders
572             lp.gravity = Gravity.BOTTOM;
573             lp.width = LayoutParams.MATCH_PARENT;
574             lp.height = hotseatBarHeightPx + mInsets.bottom;
575             hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left,
576                     hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right,
577                     hotseatBarBottomPaddingPx + mInsets.bottom);
578         }
579         hotseat.setLayoutParams(lp);
580 
581         // Layout the page indicators
582         View pageIndicator = launcher.findViewById(R.id.page_indicator);
583         if (pageIndicator != null) {
584             lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
585             if (isVerticalBarLayout()) {
586                 if (mInsets.left > 0) {
587                     lp.leftMargin = mInsets.left + pageIndicatorLandGutterLeftNavBarPx -
588                             lp.width - pageIndicatorLandWorkspaceOffsetPx;
589                 } else if (mInsets.right > 0) {
590                     lp.leftMargin = pageIndicatorLandGutterRightNavBarPx - lp.width -
591                             pageIndicatorLandWorkspaceOffsetPx;
592                 }
593                 lp.bottomMargin = workspacePadding.bottom;
594             } else {
595                 // Put the page indicators above the hotseat
596                 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
597                 lp.height = pageIndicatorHeightPx;
598                 lp.bottomMargin = hotseatBarHeightPx + mInsets.bottom;
599             }
600             pageIndicator.setLayoutParams(lp);
601         }
602 
603         // Layout the Overview Mode
604         ViewGroup overviewMode = launcher.getOverviewPanel();
605         if (overviewMode != null) {
606             int visibleChildCount = getVisibleChildCount(overviewMode);
607             int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
608             int maxWidth = totalItemWidth + (visibleChildCount - 1) * overviewModeBarSpacerWidthPx;
609 
610             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
611             lp.width = Math.min(availableWidthPx, maxWidth);
612             lp.height = getOverviewModeButtonBarHeight() + mInsets.bottom;
613             overviewMode.setLayoutParams(lp);
614         }
615 
616         if (notifyListeners) {
617             for (int i = mListeners.size() - 1; i >= 0; i--) {
618                 mListeners.get(i).onLauncherLayoutChanged();
619             }
620         }
621     }
622 
getCurrentWidth()623     private int getCurrentWidth() {
624         return isLandscape
625                 ? Math.max(widthPx, heightPx)
626                 : Math.min(widthPx, heightPx);
627     }
628 
getCurrentHeight()629     private int getCurrentHeight() {
630         return isLandscape
631                 ? Math.min(widthPx, heightPx)
632                 : Math.max(widthPx, heightPx);
633     }
634 
getCellHeight(@ontainerType int containerType)635     public int getCellHeight(@ContainerType int containerType) {
636         switch (containerType) {
637             case CellLayout.WORKSPACE:
638                 return cellHeightPx;
639             case CellLayout.FOLDER:
640                 return folderCellHeightPx;
641             case CellLayout.HOTSEAT:
642                 return hotseatCellHeightPx;
643             default:
644                 // ??
645                 return 0;
646         }
647     }
648 
649     /**
650      * @return the left/right paddings for all containers.
651      */
getContainerPadding()652     public final int[] getContainerPadding() {
653         // No paddings for portrait phone
654         if (isPhone && !isVerticalBarLayout()) {
655             return new int[] {0, 0};
656         }
657 
658         // In landscape, we match the width of the workspace
659         int padding = (pageIndicatorLandGutterRightNavBarPx +
660                 hotseatBarHeightPx + hotseatLandGutterPx + mInsets.left) / 2;
661         return new int[]{ padding, padding };
662     }
663 
shouldIgnoreLongPressToOverview(float touchX)664     public boolean shouldIgnoreLongPressToOverview(float touchX) {
665         boolean inMultiWindowMode = this != inv.landscapeProfile && this != inv.portraitProfile;
666         boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeMarginPx;
667         boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeMarginPx);
668         return !inMultiWindowMode && (touchedLhsEdge || touchedRhsEdge);
669     }
670 }
671