• 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 static com.android.launcher3.InvariantDeviceProfile.INDEX_DEFAULT;
20 import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE;
21 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
22 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
23 import static com.android.launcher3.Utilities.dpiFromPx;
24 import static com.android.launcher3.Utilities.pxFromSp;
25 import static com.android.launcher3.anim.Interpolators.LINEAR;
26 import static com.android.launcher3.config.FeatureFlags.ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH;
27 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
28 import static com.android.launcher3.icons.GraphicsUtils.getShapePath;
29 import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
30 import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
31 import static com.android.launcher3.testing.shared.ResourceUtils.roundPxValueFromFloat;
32 
33 import android.annotation.SuppressLint;
34 import android.content.Context;
35 import android.content.res.Configuration;
36 import android.content.res.Resources;
37 import android.content.res.TypedArray;
38 import android.graphics.Point;
39 import android.graphics.PointF;
40 import android.graphics.Rect;
41 import android.util.DisplayMetrics;
42 import android.util.SparseArray;
43 import android.view.Surface;
44 
45 import androidx.annotation.NonNull;
46 import androidx.annotation.Nullable;
47 import androidx.core.content.res.ResourcesCompat;
48 
49 import com.android.launcher3.CellLayout.ContainerType;
50 import com.android.launcher3.DevicePaddings.DevicePadding;
51 import com.android.launcher3.config.FeatureFlags;
52 import com.android.launcher3.icons.DotRenderer;
53 import com.android.launcher3.icons.IconNormalizer;
54 import com.android.launcher3.model.data.ItemInfo;
55 import com.android.launcher3.uioverrides.ApiWrapper;
56 import com.android.launcher3.util.DisplayController;
57 import com.android.launcher3.util.DisplayController.Info;
58 import com.android.launcher3.util.WindowBounds;
59 
60 import java.io.PrintWriter;
61 import java.util.Locale;
62 import java.util.function.Consumer;
63 
64 @SuppressLint("NewApi")
65 public class DeviceProfile {
66 
67     private static final int DEFAULT_DOT_SIZE = 100;
68     private static final float ALL_APPS_TABLET_MAX_ROWS = 5.5f;
69     private static final float MIN_FOLDER_TEXT_SIZE_SP = 16f;
70 
71     public static final PointF DEFAULT_SCALE = new PointF(1.0f, 1.0f);
72     public static final ViewScaleProvider DEFAULT_PROVIDER = itemInfo -> DEFAULT_SCALE;
73     public static final Consumer<DeviceProfile> DEFAULT_DIMENSION_PROVIDER = dp -> {};
74 
75     // Ratio of empty space, qsb should take up to appear visually centered.
76     private final float mQsbCenterFactor;
77 
78     public final InvariantDeviceProfile inv;
79     private final Info mInfo;
80     private final DisplayMetrics mMetrics;
81 
82     // Device properties
83     public final boolean isTablet;
84     public final boolean isPhone;
85     public final boolean transposeLayoutWithOrientation;
86     public final boolean isMultiDisplay;
87     public final boolean isTwoPanels;
88     public final boolean isQsbInline;
89 
90     // Device properties in current orientation
91     public final boolean isLandscape;
92     public final boolean isMultiWindowMode;
93     public final boolean isGestureMode;
94 
95     public final int windowX;
96     public final int windowY;
97     public final int widthPx;
98     public final int heightPx;
99     public final int availableWidthPx;
100     public final int availableHeightPx;
101     public final int rotationHint;
102 
103     public final float aspectRatio;
104 
105     public final boolean isScalableGrid;
106     private final int mTypeIndex;
107 
108     /**
109      * The maximum amount of left/right workspace padding as a percentage of the screen width.
110      * To be clear, this means that up to 7% of the screen width can be used as left padding, and
111      * 7% of the screen width can be used as right padding.
112      */
113     private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f;
114 
115     private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
116     private static final float TALLER_DEVICE_ASPECT_RATIO_THRESHOLD = 2.15f;
117     private static final float TALL_DEVICE_EXTRA_SPACE_THRESHOLD_DP = 252;
118     private static final float TALL_DEVICE_MORE_EXTRA_SPACE_THRESHOLD_DP = 268;
119 
120     // Workspace
121     public final int desiredWorkspaceHorizontalMarginOriginalPx;
122     public int desiredWorkspaceHorizontalMarginPx;
123     public int gridVisualizationPaddingX;
124     public int gridVisualizationPaddingY;
125     public Point cellLayoutBorderSpaceOriginalPx;
126     public Point cellLayoutBorderSpacePx;
127     public Rect cellLayoutPaddingPx = new Rect();
128 
129     public final int edgeMarginPx;
130     public final float workspaceContentScale;
131     public final int workspaceSpringLoadedMinNextPageVisiblePx;
132 
133     private final int extraSpace;
134     private int maxEmptySpace;
135     public int workspaceTopPadding;
136     public int workspaceBottomPadding;
137 
138     // Workspace page indicator
139     public final int workspacePageIndicatorHeight;
140     private final int mWorkspacePageIndicatorOverlapWorkspace;
141 
142     // Workspace icons
143     public float iconScale;
144     public int iconSizePx;
145     public int iconTextSizePx;
146     public int iconDrawablePaddingPx;
147     public int iconDrawablePaddingOriginalPx;
148 
149     public float cellScaleToFit;
150     public int cellWidthPx;
151     public int cellHeightPx;
152     public int workspaceCellPaddingXPx;
153 
154     public int cellYPaddingPx;
155 
156     // Folder
157     public float folderLabelTextScale;
158     public int folderLabelTextSizePx;
159     public int folderFooterHeightPx;
160     public int folderIconSizePx;
161     public int folderIconOffsetYPx;
162 
163     // Folder content
164     public int folderCellLayoutBorderSpacePx;
165     public int folderContentPaddingLeftRight;
166     public int folderContentPaddingTop;
167 
168     // Folder cell
169     public int folderCellWidthPx;
170     public int folderCellHeightPx;
171 
172     // Folder child
173     public int folderChildIconSizePx;
174     public int folderChildTextSizePx;
175     public int folderChildDrawablePaddingPx;
176 
177     // Hotseat
178     public int numShownHotseatIcons;
179     public int hotseatCellHeightPx;
180     public final boolean areNavButtonsInline;
181     // In portrait: size = height, in landscape: size = width
182     public int hotseatBarSizePx;
183     public int hotseatBarBottomSpacePx;
184     public int hotseatBarEndOffset;
185     public int hotseatQsbSpace;
186     public int springLoadedHotseatBarTopMarginPx;
187     // Start is the side next to the nav bar, end is the side next to the workspace
188     public final int hotseatBarSidePaddingStartPx;
189     public final int hotseatBarSidePaddingEndPx;
190     public int hotseatQsbWidth; // only used when isQsbInline
191     public final int hotseatQsbHeight;
192     public final int hotseatQsbVisualHeight;
193     private final int hotseatQsbShadowHeight;
194     public int hotseatBorderSpace;
195     private final int mMinHotseatIconSpacePx;
196     private final int mMinHotseatQsbWidthPx;
197     private final int mMaxHotseatIconSpacePx;
198     public final int inlineNavButtonsEndSpacingPx;
199 
200     // Bottom sheets
201     public int bottomSheetTopPadding;
202     public int bottomSheetOpenDuration;
203     public int bottomSheetCloseDuration;
204     public float bottomSheetWorkspaceScale;
205     public float bottomSheetDepth;
206 
207     // All apps
208     public Point allAppsBorderSpacePx;
209     public int allAppsShiftRange;
210     public int allAppsTopPadding;
211     public int allAppsOpenDuration;
212     public int allAppsCloseDuration;
213     public int allAppsCellHeightPx;
214     public int allAppsCellWidthPx;
215     public int allAppsIconSizePx;
216     public int allAppsIconDrawablePaddingPx;
217     public int allAppsLeftRightPadding;
218     public int allAppsLeftRightMargin;
219     public final int numShownAllAppsColumns;
220     public float allAppsIconTextSizePx;
221 
222     // Overview
223     public int overviewTaskMarginPx;
224     public int overviewTaskIconSizePx;
225     public int overviewTaskIconDrawableSizePx;
226     public int overviewTaskIconDrawableSizeGridPx;
227     public int overviewTaskThumbnailTopMarginPx;
228     public final int overviewActionsHeight;
229     public final int overviewActionsTopMarginPx;
230     public final int overviewActionsButtonSpacing;
231     public int overviewPageSpacing;
232     public int overviewRowSpacing;
233     public int overviewGridSideMargin;
234 
235     // Split staging
236     public int splitPlaceholderInset;
237 
238     // Widgets
239     private final ViewScaleProvider mViewScaleProvider;
240 
241     // Drop Target
242     public int dropTargetBarSizePx;
243     public int dropTargetBarTopMarginPx;
244     public int dropTargetBarBottomMarginPx;
245     public int dropTargetDragPaddingPx;
246     public int dropTargetTextSizePx;
247     public int dropTargetHorizontalPaddingPx;
248     public int dropTargetVerticalPaddingPx;
249     public int dropTargetGapPx;
250     public int dropTargetButtonWorkspaceEdgeGapPx;
251 
252     // Insets
253     private final Rect mInsets = new Rect();
254     public final Rect workspacePadding = new Rect();
255     // When true, nav bar is on the left side of the screen.
256     private boolean mIsSeascape;
257 
258     // Notification dots
259     public final DotRenderer mDotRendererWorkSpace;
260     public final DotRenderer mDotRendererAllApps;
261 
262     // Taskbar
263     public boolean isTaskbarPresent;
264     // Whether Taskbar will inset the bottom of apps by taskbarSize.
265     public boolean isTaskbarPresentInApps;
266     public final int taskbarHeight;
267     public final int stashedTaskbarHeight;
268     public final int taskbarBottomMargin;
269     public final int taskbarIconSize;
270     // If true, used to layout taskbar in 3 button navigation mode.
271     public final boolean startAlignTaskbar;
272 
273     // DragController
274     public int flingToDeleteThresholdVelocity;
275 
276     /** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */
DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds, SparseArray<DotRenderer> dotRendererCache, boolean isMultiWindowMode, boolean transposeLayoutWithOrientation, boolean isMultiDisplay, boolean isGestureMode, @NonNull final ViewScaleProvider viewScaleProvider, @NonNull final Consumer<DeviceProfile> dimensionOverrideProvider)277     DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
278             SparseArray<DotRenderer> dotRendererCache, boolean isMultiWindowMode,
279             boolean transposeLayoutWithOrientation, boolean isMultiDisplay, boolean isGestureMode,
280             @NonNull final ViewScaleProvider viewScaleProvider,
281             @NonNull final Consumer<DeviceProfile> dimensionOverrideProvider) {
282 
283         this.inv = inv;
284         this.isLandscape = windowBounds.isLandscape();
285         this.isMultiWindowMode = isMultiWindowMode;
286         this.transposeLayoutWithOrientation = transposeLayoutWithOrientation;
287         this.isMultiDisplay = isMultiDisplay;
288         this.isGestureMode = isGestureMode;
289         windowX = windowBounds.bounds.left;
290         windowY = windowBounds.bounds.top;
291         this.rotationHint = windowBounds.rotationHint;
292         mInsets.set(windowBounds.insets);
293 
294         isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
295         // Determine device posture.
296         mInfo = info;
297         isTablet = info.isTablet(windowBounds);
298         isPhone = !isTablet;
299         isTwoPanels = isTablet && isMultiDisplay;
300         isTaskbarPresent = isTablet && ApiWrapper.TASKBAR_DRAWN_IN_PROCESS;
301 
302         // Some more constants.
303         context = getContext(context, info, isVerticalBarLayout() || (isTablet && isLandscape)
304                         ? Configuration.ORIENTATION_LANDSCAPE
305                         : Configuration.ORIENTATION_PORTRAIT,
306                 windowBounds);
307         final Resources res = context.getResources();
308         mMetrics = res.getDisplayMetrics();
309 
310         // Determine sizes.
311         widthPx = windowBounds.bounds.width();
312         heightPx = windowBounds.bounds.height();
313         availableWidthPx = windowBounds.availableSize.x;
314         availableHeightPx = windowBounds.availableSize.y;
315 
316         aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
317         boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
318         mQsbCenterFactor = res.getFloat(R.dimen.qsb_center_factor);
319 
320         if (isTwoPanels) {
321             if (isLandscape) {
322                 mTypeIndex = INDEX_TWO_PANEL_LANDSCAPE;
323             } else {
324                 mTypeIndex = INDEX_TWO_PANEL_PORTRAIT;
325             }
326         } else {
327             if (isLandscape) {
328                 mTypeIndex = INDEX_LANDSCAPE;
329             } else {
330                 mTypeIndex = INDEX_DEFAULT;
331             }
332         }
333 
334         if (DisplayController.isTransientTaskbar(context)) {
335             float invTransientIconSizeDp = inv.transientTaskbarIconSize[mTypeIndex];
336             taskbarIconSize = pxFromDp(invTransientIconSizeDp, mMetrics);
337             taskbarHeight = taskbarIconSize
338                     + (2 * res.getDimensionPixelSize(R.dimen.transient_taskbar_padding));
339             stashedTaskbarHeight =
340                     res.getDimensionPixelSize(R.dimen.transient_taskbar_stashed_height);
341             taskbarBottomMargin =
342                     res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin);
343             startAlignTaskbar = false;
344         } else {
345             taskbarIconSize = pxFromDp(ResourcesCompat.getFloat(res, R.dimen.taskbar_icon_size),
346                     mMetrics);
347             taskbarHeight = res.getDimensionPixelSize(R.dimen.taskbar_size);
348             stashedTaskbarHeight = res.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
349             taskbarBottomMargin = 0;
350             startAlignTaskbar = inv.startAlignTaskbar[mTypeIndex];
351         }
352 
353         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
354         workspaceContentScale = res.getFloat(R.dimen.workspace_content_scale);
355 
356         desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
357         desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx;
358         gridVisualizationPaddingX = res.getDimensionPixelSize(
359                 R.dimen.grid_visualization_horizontal_cell_spacing);
360         gridVisualizationPaddingY = res.getDimensionPixelSize(
361                 R.dimen.grid_visualization_vertical_cell_spacing);
362 
363         bottomSheetTopPadding = mInsets.top // statusbar height
364                 + res.getDimensionPixelSize(R.dimen.bottom_sheet_extra_top_padding)
365                 + (isTablet ? 0 : edgeMarginPx); // phones need edgeMarginPx additional padding
366         bottomSheetOpenDuration = res.getInteger(R.integer.config_bottomSheetOpenDuration);
367         bottomSheetCloseDuration = res.getInteger(R.integer.config_bottomSheetCloseDuration);
368         if (isTablet) {
369             bottomSheetWorkspaceScale = workspaceContentScale;
370             if (isMultiDisplay && !ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH.get()) {
371                 // TODO(b/259893832): Revert to use maxWallpaperScale to calculate bottomSheetDepth
372                 // when screen recorder bug is fixed.
373                 bottomSheetDepth = 1f;
374             } else {
375                 // The goal is to set wallpaper to zoom at workspaceContentScale when in AllApps.
376                 // When depth is 0, wallpaper zoom is set to maxWallpaperScale.
377                 // When depth is 1, wallpaper zoom is set to 1.
378                 // For depth to achieve zoom set to maxWallpaperScale * workspaceContentScale:
379                 float maxWallpaperScale = res.getFloat(R.dimen.config_wallpaperMaxScale);
380                 bottomSheetDepth = Utilities.mapToRange(maxWallpaperScale * workspaceContentScale,
381                         maxWallpaperScale, 1f, 0f, 1f, LINEAR);
382             }
383         } else {
384             bottomSheetWorkspaceScale = 1f;
385             bottomSheetDepth = 0f;
386         }
387 
388         folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
389 
390         if (isScalableGrid && inv.folderStyle != INVALID_RESOURCE_HANDLE) {
391             TypedArray folderStyle = context.obtainStyledAttributes(inv.folderStyle,
392                     R.styleable.FolderStyle);
393             // These are re-set in #updateFolderCellSize if the grid is not scalable
394             folderCellHeightPx = folderStyle.getDimensionPixelSize(
395                     R.styleable.FolderStyle_folderCellHeight, 0);
396             folderCellWidthPx = folderStyle.getDimensionPixelSize(
397                     R.styleable.FolderStyle_folderCellWidth, 0);
398 
399             folderContentPaddingTop = folderStyle.getDimensionPixelSize(
400                     R.styleable.FolderStyle_folderTopPadding, 0);
401             folderCellLayoutBorderSpacePx = folderStyle.getDimensionPixelSize(
402                     R.styleable.FolderStyle_folderBorderSpace, 0);
403             folderFooterHeightPx = folderStyle.getDimensionPixelSize(
404                     R.styleable.FolderStyle_folderFooterHeight, 0);
405             folderStyle.recycle();
406         } else {
407             folderCellLayoutBorderSpacePx = 0;
408             folderFooterHeightPx = res.getDimensionPixelSize(R.dimen.folder_footer_height_default);
409             folderContentPaddingTop = res.getDimensionPixelSize(R.dimen.folder_top_padding_default);
410         }
411 
412         cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv);
413         cellLayoutBorderSpaceOriginalPx = new Point(cellLayoutBorderSpacePx);
414         allAppsBorderSpacePx = new Point(
415                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics),
416                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics));
417         setupAllAppsStyle(context);
418 
419         workspacePageIndicatorHeight = res.getDimensionPixelSize(
420                 R.dimen.workspace_page_indicator_height);
421         mWorkspacePageIndicatorOverlapWorkspace =
422                 res.getDimensionPixelSize(R.dimen.workspace_page_indicator_overlap_workspace);
423 
424         TypedArray cellStyle;
425         if (inv.cellStyle != INVALID_RESOURCE_HANDLE) {
426             cellStyle = context.obtainStyledAttributes(inv.cellStyle,
427                     R.styleable.CellStyle);
428         } else {
429             cellStyle = context.obtainStyledAttributes(R.style.CellStyleDefault,
430                     R.styleable.CellStyle);
431         }
432         iconDrawablePaddingOriginalPx = cellStyle.getDimensionPixelSize(
433                 R.styleable.CellStyle_iconDrawablePadding, 0);
434         cellStyle.recycle();
435 
436         dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
437         dropTargetBarTopMarginPx = res.getDimensionPixelSize(R.dimen.drop_target_top_margin);
438         dropTargetBarBottomMarginPx = res.getDimensionPixelSize(R.dimen.drop_target_bottom_margin);
439         dropTargetDragPaddingPx = res.getDimensionPixelSize(R.dimen.drop_target_drag_padding);
440         dropTargetTextSizePx = res.getDimensionPixelSize(R.dimen.drop_target_text_size);
441         dropTargetHorizontalPaddingPx = res.getDimensionPixelSize(
442                 R.dimen.drop_target_button_drawable_horizontal_padding);
443         dropTargetVerticalPaddingPx = res.getDimensionPixelSize(
444                 R.dimen.drop_target_button_drawable_vertical_padding);
445         dropTargetGapPx = res.getDimensionPixelSize(R.dimen.drop_target_button_gap);
446         dropTargetButtonWorkspaceEdgeGapPx = res.getDimensionPixelSize(
447                 R.dimen.drop_target_button_workspace_edge_gap);
448 
449         workspaceSpringLoadedMinNextPageVisiblePx = res.getDimensionPixelSize(
450                 R.dimen.dynamic_grid_spring_loaded_min_next_space_visible);
451 
452         workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
453 
454         hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height);
455         hotseatQsbShadowHeight = res.getDimensionPixelSize(R.dimen.qsb_shadow_height);
456         hotseatQsbVisualHeight = hotseatQsbHeight - 2 * hotseatQsbShadowHeight;
457 
458         // Whether QSB might be inline in appropriate orientation (e.g. landscape).
459         boolean canQsbInline = (isTwoPanels ? inv.inlineQsb[INDEX_TWO_PANEL_PORTRAIT]
460                 || inv.inlineQsb[INDEX_TWO_PANEL_LANDSCAPE]
461                 : inv.inlineQsb[INDEX_DEFAULT] || inv.inlineQsb[INDEX_LANDSCAPE])
462                 && hotseatQsbHeight > 0;
463         isQsbInline = isScalableGrid && inv.inlineQsb[mTypeIndex] && canQsbInline;
464 
465         areNavButtonsInline = isTaskbarPresent && !isGestureMode;
466         numShownHotseatIcons =
467                 isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons;
468 
469         numShownAllAppsColumns =
470                 isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns;
471 
472         int hotseatBarBottomSpace = pxFromDp(inv.hotseatBarBottomSpace[mTypeIndex], mMetrics);
473         int minQsbMargin = res.getDimensionPixelSize(R.dimen.min_qsb_margin);
474         hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics);
475         // Have a little space between the inset and the QSB
476         if (mInsets.bottom + minQsbMargin > hotseatBarBottomSpace) {
477             int availableSpace = hotseatQsbSpace - (mInsets.bottom - hotseatBarBottomSpace);
478 
479             // Only change the spaces if there is space
480             if (availableSpace > 0) {
481                 // Make sure there is enough space between hotseat/QSB and QSB/navBar
482                 if (availableSpace < minQsbMargin * 2) {
483                     minQsbMargin = availableSpace / 2;
484                     hotseatQsbSpace = minQsbMargin;
485                 } else {
486                     hotseatQsbSpace -= minQsbMargin;
487                 }
488             }
489             hotseatBarBottomSpacePx = mInsets.bottom + minQsbMargin;
490 
491         } else {
492             hotseatBarBottomSpacePx = hotseatBarBottomSpace;
493         }
494 
495         springLoadedHotseatBarTopMarginPx = res.getDimensionPixelSize(
496                 R.dimen.spring_loaded_hotseat_top_margin);
497         hotseatBarSidePaddingEndPx =
498                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
499         // Add a bit of space between nav bar and hotseat in vertical bar layout.
500         hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
501         updateHotseatSizes(pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
502         if (areNavButtonsInline && !isPhone) {
503             inlineNavButtonsEndSpacingPx =
504                     res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing);
505             /*
506              * 3 nav buttons +
507              * Spacing between nav buttons +
508              * Space at the end for contextual buttons
509              */
510             hotseatBarEndOffset = 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
511                     + 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween)
512                     + inlineNavButtonsEndSpacingPx;
513         } else {
514             inlineNavButtonsEndSpacingPx = 0;
515             hotseatBarEndOffset = 0;
516         }
517 
518         overviewTaskMarginPx = res.getDimensionPixelSize(R.dimen.overview_task_margin);
519         overviewTaskIconSizePx = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
520         overviewTaskIconDrawableSizePx =
521                 res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size);
522         overviewTaskIconDrawableSizeGridPx =
523                 res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid);
524         overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx;
525         overviewActionsTopMarginPx = res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
526         overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
527         overviewActionsButtonSpacing = res.getDimensionPixelSize(
528                 R.dimen.overview_actions_button_spacing);
529         overviewActionsHeight = res.getDimensionPixelSize(R.dimen.overview_actions_height);
530         overviewRowSpacing = res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing);
531         overviewGridSideMargin = res.getDimensionPixelSize(R.dimen.overview_grid_side_margin);
532 
533         splitPlaceholderInset = res.getDimensionPixelSize(R.dimen.split_placeholder_inset);
534 
535         // Calculate all of the remaining variables.
536         extraSpace = updateAvailableDimensions(res);
537 
538         // Now that we have all of the variables calculated, we can tune certain sizes.
539         if (isScalableGrid && inv.devicePaddingId != INVALID_RESOURCE_HANDLE) {
540             // Paddings were created assuming no scaling, so we first unscale the extra space.
541             int unscaledExtraSpace = (int) (extraSpace / cellScaleToFit);
542             DevicePaddings devicePaddings = new DevicePaddings(context, inv.devicePaddingId);
543             DevicePadding padding = devicePaddings.getDevicePadding(unscaledExtraSpace);
544             maxEmptySpace = padding.getMaxEmptySpacePx();
545 
546             int paddingWorkspaceTop = padding.getWorkspaceTopPadding(unscaledExtraSpace);
547             int paddingWorkspaceBottom = padding.getWorkspaceBottomPadding(unscaledExtraSpace);
548             int paddingHotseatBottom = padding.getHotseatBottomPadding(unscaledExtraSpace);
549 
550             workspaceTopPadding = Math.round(paddingWorkspaceTop * cellScaleToFit);
551             workspaceBottomPadding = Math.round(paddingWorkspaceBottom * cellScaleToFit);
552         }
553 
554         int cellLayoutPadding =
555                 isTwoPanels ? cellLayoutBorderSpacePx.x / 2 : res.getDimensionPixelSize(
556                         R.dimen.cell_layout_padding);
557         cellLayoutPaddingPx = new Rect(cellLayoutPadding, cellLayoutPadding, cellLayoutPadding,
558                 cellLayoutPadding);
559         updateWorkspacePadding();
560 
561         // Folder scaling requires correct workspace paddings
562         updateAvailableFolderCellDimensions(res);
563 
564         mMinHotseatIconSpacePx = res.getDimensionPixelSize(R.dimen.min_hotseat_icon_space);
565         mMinHotseatQsbWidthPx = res.getDimensionPixelSize(R.dimen.min_hotseat_qsb_width);
566         mMaxHotseatIconSpacePx = areNavButtonsInline
567                 ? res.getDimensionPixelSize(R.dimen.max_hotseat_icon_space) : Integer.MAX_VALUE;
568         // Hotseat and QSB width depends on updated cellSize and workspace padding
569         recalculateHotseatWidthAndBorderSpace();
570 
571         // AllApps height calculation depends on updated cellSize
572         if (isTablet) {
573             int collapseHandleHeight =
574                     res.getDimensionPixelOffset(R.dimen.bottom_sheet_handle_area_height);
575             int contentHeight = heightPx - collapseHandleHeight - hotseatQsbHeight;
576             int targetContentHeight = (int) (allAppsCellHeightPx * ALL_APPS_TABLET_MAX_ROWS);
577             allAppsTopPadding = Math.max(mInsets.top, contentHeight - targetContentHeight);
578             allAppsShiftRange = heightPx - allAppsTopPadding;
579         } else {
580             allAppsTopPadding = 0;
581             allAppsShiftRange =
582                     res.getDimensionPixelSize(R.dimen.all_apps_starting_vertical_translate);
583         }
584         allAppsOpenDuration = res.getInteger(R.integer.config_allAppsOpenDuration);
585         allAppsCloseDuration = res.getInteger(R.integer.config_allAppsCloseDuration);
586 
587         flingToDeleteThresholdVelocity = res.getDimensionPixelSize(
588                 R.dimen.drag_flingToDeleteMinVelocity);
589 
590         mViewScaleProvider = viewScaleProvider;
591 
592         dimensionOverrideProvider.accept(this);
593 
594         // This is done last, after iconSizePx is calculated above.
595         mDotRendererWorkSpace = createDotRenderer(context, iconSizePx, dotRendererCache);
596         mDotRendererAllApps = createDotRenderer(context, allAppsIconSizePx, dotRendererCache);
597     }
598 
createDotRenderer( @onNull Context context, int size, @NonNull SparseArray<DotRenderer> cache)599     private static DotRenderer createDotRenderer(
600             @NonNull Context context, int size, @NonNull SparseArray<DotRenderer> cache) {
601         DotRenderer renderer = cache.get(size);
602         if (renderer == null) {
603             renderer = new DotRenderer(size, getShapePath(context, DEFAULT_DOT_SIZE),
604                     DEFAULT_DOT_SIZE);
605             cache.put(size, renderer);
606         }
607         return renderer;
608     }
609 
610     /**
611      * QSB width is always calculated because when in 3 button nav the width doesn't follow the
612      * width of the hotseat.
613      */
calculateQsbWidth(int hotseatBorderSpace)614     private int calculateQsbWidth(int hotseatBorderSpace) {
615         if (isQsbInline) {
616             int columns = getPanelCount() * inv.numColumns;
617             return getIconToIconWidthForColumns(columns)
618                     - iconSizePx * numShownHotseatIcons
619                     - hotseatBorderSpace * numShownHotseatIcons;
620         } else {
621             int columns = inv.hotseatColumnSpan[mTypeIndex];
622             return getIconToIconWidthForColumns(columns);
623         }
624     }
625 
getIconToIconWidthForColumns(int columns)626     private int getIconToIconWidthForColumns(int columns) {
627         return columns * getCellSize().x
628                 + (columns - 1) * cellLayoutBorderSpacePx.x
629                 - getCellHorizontalSpace();
630     }
631 
getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res)632     private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) {
633         if (isVerticalBarLayout()) {
634             return 0;
635         }
636 
637         return isScalableGrid
638                 ? pxFromDp(idp.horizontalMargin[mTypeIndex], mMetrics)
639                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_left_right_margin);
640     }
641 
642     /** Updates hotseatCellHeightPx and hotseatBarSizePx */
updateHotseatSizes(int hotseatIconSizePx)643     private void updateHotseatSizes(int hotseatIconSizePx) {
644         // Ensure there is enough space for folder icons, which have a slightly larger radius.
645         hotseatCellHeightPx = (int) Math.ceil(hotseatIconSizePx * ICON_OVERLAP_FACTOR);
646 
647         if (isVerticalBarLayout()) {
648             hotseatBarSizePx = hotseatIconSizePx + hotseatBarSidePaddingStartPx
649                     + hotseatBarSidePaddingEndPx;
650         } else if (isQsbInline) {
651             hotseatBarSizePx = Math.max(hotseatIconSizePx, hotseatQsbVisualHeight)
652                     + hotseatBarBottomSpacePx;
653         } else {
654             hotseatBarSizePx = hotseatIconSizePx
655                     + hotseatQsbSpace
656                     + hotseatQsbVisualHeight
657                     + hotseatBarBottomSpacePx;
658         }
659     }
660 
661     /**
662      * Calculates the width of the hotseat, changing spaces between the icons and removing icons if
663      * necessary.
664      */
recalculateHotseatWidthAndBorderSpace()665     public void recalculateHotseatWidthAndBorderSpace() {
666         if (!isScalableGrid) return;
667 
668         int columns = inv.hotseatColumnSpan[mTypeIndex];
669         float hotseatWidthPx = getIconToIconWidthForColumns(columns);
670         hotseatBorderSpace = calculateHotseatBorderSpace(hotseatWidthPx, /* numExtraBorder= */ 0);
671         hotseatQsbWidth = calculateQsbWidth(hotseatBorderSpace);
672         // Spaces should be correct when the nav buttons are not inline
673         if (!areNavButtonsInline) {
674             return;
675         }
676 
677         // The side space with inline buttons should be what is defined in InvariantDeviceProfile
678         int sideSpacePx = inlineNavButtonsEndSpacingPx;
679         int maxHotseatWidthPx = availableWidthPx - sideSpacePx - hotseatBarEndOffset;
680         int maxHotseatIconsWidthPx = maxHotseatWidthPx - (isQsbInline ? hotseatQsbWidth : 0);
681         hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx,
682                 (isQsbInline ? 1 : 0) + /* border between nav buttons and first icon */ 1);
683 
684         if (hotseatBorderSpace >= mMinHotseatIconSpacePx) {
685             return;
686         }
687 
688         // Border space can't be less than the minimum
689         hotseatBorderSpace = mMinHotseatIconSpacePx;
690         int requiredWidth = getHotseatRequiredWidth();
691 
692         // If there is an inline qsb, change its size
693         if (isQsbInline) {
694             hotseatQsbWidth -= requiredWidth - maxHotseatWidthPx;
695             if (hotseatQsbWidth >= mMinHotseatQsbWidthPx) {
696                 return;
697             }
698 
699             // QSB can't be less than the minimum
700             hotseatQsbWidth = mMinHotseatQsbWidthPx;
701         }
702 
703         maxHotseatIconsWidthPx = maxHotseatWidthPx - (isQsbInline ? hotseatQsbWidth : 0);
704 
705         // If it still doesn't fit, start removing icons
706         do {
707             numShownHotseatIcons--;
708             hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx,
709                     (isQsbInline ? 1 : 0) + /* border between nav buttons and first icon */ 1);
710         } while (hotseatBorderSpace < mMinHotseatIconSpacePx && numShownHotseatIcons > 1);
711 
712     }
713 
getCellLayoutBorderSpace(InvariantDeviceProfile idp)714     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
715         return getCellLayoutBorderSpace(idp, 1f);
716     }
717 
getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale)718     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale) {
719         if (!isScalableGrid) {
720             return new Point(0, 0);
721         }
722 
723         int horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics, scale);
724         int verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics, scale);
725 
726         return new Point(horizontalSpacePx, verticalSpacePx);
727     }
728 
getDisplayInfo()729     public Info getDisplayInfo() {
730         return mInfo;
731     }
732 
733     /**
734      * We inset the widget padding added by the system and instead rely on the border spacing
735      * between cells to create reliable consistency between widgets
736      */
shouldInsetWidgets()737     public boolean shouldInsetWidgets() {
738         Rect widgetPadding = inv.defaultWidgetPadding;
739 
740         // Check all sides to ensure that the widget won't overlap into another cell, or into
741         // status bar.
742         return workspaceTopPadding > widgetPadding.top
743                 && cellLayoutBorderSpacePx.x > widgetPadding.left
744                 && cellLayoutBorderSpacePx.y > widgetPadding.top
745                 && cellLayoutBorderSpacePx.x > widgetPadding.right
746                 && cellLayoutBorderSpacePx.y > widgetPadding.bottom;
747     }
748 
toBuilder(Context context)749     public Builder toBuilder(Context context) {
750         WindowBounds bounds = new WindowBounds(
751                 widthPx, heightPx, availableWidthPx, availableHeightPx, rotationHint);
752         bounds.bounds.offsetTo(windowX, windowY);
753         bounds.insets.set(mInsets);
754 
755         SparseArray<DotRenderer> dotRendererCache = new SparseArray<>();
756         dotRendererCache.put(iconSizePx, mDotRendererWorkSpace);
757         dotRendererCache.put(allAppsIconSizePx, mDotRendererAllApps);
758 
759         return new Builder(context, inv, mInfo)
760                 .setWindowBounds(bounds)
761                 .setIsMultiDisplay(isMultiDisplay)
762                 .setMultiWindowMode(isMultiWindowMode)
763                 .setDotRendererCache(dotRendererCache)
764                 .setGestureMode(isGestureMode);
765     }
766 
copy(Context context)767     public DeviceProfile copy(Context context) {
768         return toBuilder(context).build();
769     }
770 
771     /**
772      * TODO: Move this to the builder as part of setMultiWindowMode
773      */
getMultiWindowProfile(Context context, WindowBounds windowBounds)774     public DeviceProfile getMultiWindowProfile(Context context, WindowBounds windowBounds) {
775         DeviceProfile profile = toBuilder(context)
776                 .setWindowBounds(windowBounds)
777                 .setMultiWindowMode(true)
778                 .build();
779 
780         // We use these scales to measure and layout the widgets using their full invariant profile
781         // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
782         float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
783         float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
784         if (appWidgetScaleX != 1 || appWidgetScaleY != 1) {
785             final PointF p = new PointF(appWidgetScaleX, appWidgetScaleY);
786             profile = profile.toBuilder(context)
787                     .setViewScaleProvider(i -> p)
788                     .build();
789         }
790 
791         profile.hideWorkspaceLabelsIfNotEnoughSpace();
792 
793         return profile;
794     }
795 
796     /**
797      * Checks if there is enough space for labels on the workspace.
798      * If there is not, labels on the Workspace are hidden.
799      * It is important to call this method after the All Apps variables have been set.
800      */
hideWorkspaceLabelsIfNotEnoughSpace()801     private void hideWorkspaceLabelsIfNotEnoughSpace() {
802         float iconTextHeight = Utilities.calculateTextHeight(iconTextSizePx);
803         float workspaceCellPaddingY = getCellSize().y - iconSizePx - iconDrawablePaddingPx
804                 - iconTextHeight;
805 
806         // We want enough space so that the text is closer to its corresponding icon.
807         if (workspaceCellPaddingY < iconTextHeight) {
808             iconTextSizePx = 0;
809             iconDrawablePaddingPx = 0;
810             cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR);
811             autoResizeAllAppsCells();
812         }
813     }
814 
815     /**
816      * Re-computes the all-apps cell size to be independent of workspace
817      */
autoResizeAllAppsCells()818     public void autoResizeAllAppsCells() {
819         int textHeight = Utilities.calculateTextHeight(allAppsIconTextSizePx);
820         int topBottomPadding = textHeight;
821         allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
822                 + textHeight + (topBottomPadding * 2);
823     }
824 
updateAllAppsContainerWidth(Resources res)825     private void updateAllAppsContainerWidth(Resources res) {
826         int cellLayoutHorizontalPadding =
827                 (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right) / 2;
828         if (isTablet) {
829             int usedWidth = (allAppsCellWidthPx * numShownAllAppsColumns)
830                     + (allAppsBorderSpacePx.x * (numShownAllAppsColumns - 1))
831                     + allAppsLeftRightPadding * 2;
832             allAppsLeftRightMargin = Math.max(1, (availableWidthPx - usedWidth) / 2);
833         } else {
834             allAppsLeftRightPadding =
835                     desiredWorkspaceHorizontalMarginPx + cellLayoutHorizontalPadding;
836         }
837     }
838 
setupAllAppsStyle(Context context)839     private void setupAllAppsStyle(Context context) {
840         TypedArray allAppsStyle;
841         if (inv.allAppsStyle != INVALID_RESOURCE_HANDLE) {
842             allAppsStyle = context.obtainStyledAttributes(inv.allAppsStyle,
843                     R.styleable.AllAppsStyle);
844         } else {
845             allAppsStyle = context.obtainStyledAttributes(R.style.AllAppsStyleDefault,
846                     R.styleable.AllAppsStyle);
847         }
848         allAppsLeftRightPadding = allAppsStyle.getDimensionPixelSize(
849                 R.styleable.AllAppsStyle_horizontalPadding, 0);
850         allAppsStyle.recycle();
851     }
852 
853     /**
854      * Returns the amount of extra (or unused) vertical space.
855      */
updateAvailableDimensions(Resources res)856     private int updateAvailableDimensions(Resources res) {
857         float invIconSizeDp = inv.iconSize[mTypeIndex];
858         float invIconTextSizeSp = inv.iconTextSize[mTypeIndex];
859         iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics));
860         iconTextSizePx = pxFromSp(invIconTextSizeSp, mMetrics);
861 
862         updateIconSize(1f, res);
863 
864         updateWorkspacePadding();
865 
866         // Check to see if the icons fit within the available height.
867         float usedHeight = getCellLayoutHeightSpecification();
868         final int maxHeight = getCellLayoutHeight();
869         float extraHeight = Math.max(0, maxHeight - usedHeight);
870         float scaleY = maxHeight / usedHeight;
871         boolean shouldScale = scaleY < 1f;
872 
873         float scaleX = 1f;
874         if (isScalableGrid) {
875             // We scale to fit the cellWidth and cellHeight in the available space.
876             // The benefit of scalable grids is that we can get consistent aspect ratios between
877             // devices.
878             float usedWidth =
879                     getCellLayoutWidthSpecification() + (desiredWorkspaceHorizontalMarginPx * 2);
880             // We do not subtract padding here, as we also scale the workspace padding if needed.
881             scaleX = availableWidthPx / usedWidth;
882             shouldScale = true;
883         }
884 
885         if (shouldScale) {
886             float scale = Math.min(scaleX, scaleY);
887             updateIconSize(scale, res);
888             extraHeight = Math.max(0, maxHeight - getCellLayoutHeightSpecification());
889         }
890 
891         return Math.round(extraHeight);
892     }
893 
894     private int getCellLayoutHeightSpecification() {
895         return (cellHeightPx * inv.numRows) + (cellLayoutBorderSpacePx.y * (inv.numRows - 1))
896                 + cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom;
897     }
898 
899     private int getCellLayoutWidthSpecification() {
900         int numColumns = getPanelCount() * inv.numColumns;
901         return (cellWidthPx * numColumns) + (cellLayoutBorderSpacePx.x * (numColumns - 1))
902                 + cellLayoutPaddingPx.left + cellLayoutPaddingPx.right;
903     }
904 
905     /**
906      * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx,
907      * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
908      * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
909      */
910     public void updateIconSize(float scale, Resources res) {
911         // Icon scale should never exceed 1, otherwise pixellation may occur.
912         iconScale = Math.min(1f, scale);
913         cellScaleToFit = scale;
914 
915         // Workspace
916         final boolean isVerticalLayout = isVerticalBarLayout();
917         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * iconScale);
918         cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv, scale);
919 
920         if (isScalableGrid) {
921             cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale);
922             cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale);
923 
924             if (cellWidthPx < iconSizePx) {
925                 // If cellWidth no longer fit iconSize, reduce borderSpace to make cellWidth bigger.
926                 int numColumns = getPanelCount() * inv.numColumns;
927                 int numBorders = numColumns - 1;
928                 int extraWidthRequired = (iconSizePx - cellWidthPx) * numColumns;
929                 if (cellLayoutBorderSpacePx.x * numBorders >= extraWidthRequired) {
930                     cellWidthPx = iconSizePx;
931                     cellLayoutBorderSpacePx.x -= extraWidthRequired / numBorders;
932                 } else {
933                     // If it still doesn't fit, set borderSpace to 0 and distribute the space for
934                     // cellWidth, and reduce iconSize.
935                     cellWidthPx = (cellWidthPx * numColumns
936                             + cellLayoutBorderSpacePx.x * numBorders) / numColumns;
937                     iconSizePx = Math.min(iconSizePx, cellWidthPx);
938                     cellLayoutBorderSpacePx.x = 0;
939                 }
940             }
941 
942             int cellTextAndPaddingHeight =
943                     iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
944             int cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
945             if (cellHeightPx < cellContentHeight) {
946                 // If cellHeight no longer fit iconSize, reduce borderSpace to make cellHeight
947                 // bigger.
948                 int numBorders = inv.numRows - 1;
949                 int extraHeightRequired = (cellContentHeight - cellHeightPx) * inv.numRows;
950                 if (cellLayoutBorderSpacePx.y * numBorders >= extraHeightRequired) {
951                     cellHeightPx = cellContentHeight;
952                     cellLayoutBorderSpacePx.y -= extraHeightRequired / numBorders;
953                 } else {
954                     // If it still doesn't fit, set borderSpace to 0 to recover space.
955                     cellHeightPx = (cellHeightPx * inv.numRows
956                             + cellLayoutBorderSpacePx.y * numBorders) / inv.numRows;
957                     cellLayoutBorderSpacePx.y = 0;
958                     // Reduce iconDrawablePaddingPx to make cellContentHeight smaller.
959                     int cellContentWithoutPadding = cellContentHeight - iconDrawablePaddingPx;
960                     if (cellContentWithoutPadding <= cellHeightPx) {
961                         iconDrawablePaddingPx = cellContentHeight - cellHeightPx;
962                     } else {
963                         // If it still doesn't fit, set iconDrawablePaddingPx to 0 to recover space,
964                         // then proportional reduce iconSizePx and iconTextSizePx to fit.
965                         iconDrawablePaddingPx = 0;
966                         float ratio = cellHeightPx / (float) cellContentWithoutPadding;
967                         iconSizePx = (int) (iconSizePx * ratio);
968                         iconTextSizePx = (int) (iconTextSizePx * ratio);
969                     }
970                     cellTextAndPaddingHeight =
971                             iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
972                 }
973                 cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
974             }
975             cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
976             desiredWorkspaceHorizontalMarginPx =
977                     (int) (desiredWorkspaceHorizontalMarginOriginalPx * scale);
978         } else {
979             cellWidthPx = iconSizePx + iconDrawablePaddingPx;
980             cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR)
981                     + iconDrawablePaddingPx
982                     + Utilities.calculateTextHeight(iconTextSizePx);
983             int cellPaddingY = (getCellSize().y - cellHeightPx) / 2;
984             if (iconDrawablePaddingPx > cellPaddingY && !isVerticalLayout
985                     && !isMultiWindowMode) {
986                 // Ensures that the label is closer to its corresponding icon. This is not an issue
987                 // with vertical bar layout or multi-window mode since the issue is handled
988                 // separately with their calls to {@link #adjustToHideWorkspaceLabels}.
989                 cellHeightPx -= (iconDrawablePaddingPx - cellPaddingY);
990                 iconDrawablePaddingPx = cellPaddingY;
991             }
992         }
993 
994         // All apps
995         updateAllAppsIconSize(scale, res);
996 
997         updateHotseatSizes(iconSizePx);
998 
999         // Folder icon
1000         folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
1001         folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
1002     }
1003 
1004     /**
1005      * This method calculates the space between the icons to achieve a certain width.
1006      */
1007     private int calculateHotseatBorderSpace(float hotseatWidthPx, int numExtraBorder) {
1008         float hotseatIconsTotalPx = iconSizePx * numShownHotseatIcons;
1009         int hotseatBorderSpacePx =
1010                 (int) (hotseatWidthPx - hotseatIconsTotalPx)
1011                         / (numShownHotseatIcons - 1 + numExtraBorder);
1012         return Math.min(hotseatBorderSpacePx, mMaxHotseatIconSpacePx);
1013     }
1014 
1015 
1016     /**
1017      * Updates the iconSize for allApps* variants.
1018      */
1019     private void updateAllAppsIconSize(float scale, Resources res) {
1020         allAppsBorderSpacePx = new Point(
1021                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics, scale),
1022                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics, scale));
1023         // AllApps cells don't have real space between cells,
1024         // so we add the border space to the cell height
1025         allAppsCellHeightPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].y, mMetrics)
1026                 + allAppsBorderSpacePx.y;
1027         // but width is just the cell,
1028         // the border is added in #updateAllAppsContainerWidth
1029         if (isScalableGrid) {
1030             allAppsIconSizePx = pxFromDp(inv.allAppsIconSize[mTypeIndex], mMetrics);
1031             allAppsIconTextSizePx = pxFromSp(inv.allAppsIconTextSize[mTypeIndex], mMetrics);
1032             allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
1033             allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics, scale);
1034 
1035             if (allAppsCellWidthPx < allAppsIconSizePx) {
1036                 // If allAppsCellWidth no longer fit allAppsIconSize, reduce allAppsBorderSpace to
1037                 // make allAppsCellWidth bigger.
1038                 int numBorders = inv.numAllAppsColumns - 1;
1039                 int extraWidthRequired =
1040                         (allAppsIconSizePx - allAppsCellWidthPx) * inv.numAllAppsColumns;
1041                 if (allAppsBorderSpacePx.x * numBorders >= extraWidthRequired) {
1042                     allAppsCellWidthPx = allAppsIconSizePx;
1043                     allAppsBorderSpacePx.x -= extraWidthRequired / numBorders;
1044                 } else {
1045                     // If it still doesn't fit, set allAppsBorderSpace to 0 and distribute the space
1046                     // for allAppsCellWidth, and reduce allAppsIconSize.
1047                     allAppsCellWidthPx = (allAppsCellWidthPx * inv.numAllAppsColumns
1048                             + allAppsBorderSpacePx.x * numBorders) / inv.numAllAppsColumns;
1049                     allAppsIconSizePx = Math.min(allAppsIconSizePx, allAppsCellWidthPx);
1050                     allAppsBorderSpacePx.x = 0;
1051                 }
1052             }
1053 
1054             int cellContentHeight = allAppsIconSizePx
1055                     + Utilities.calculateTextHeight(allAppsIconTextSizePx) + allAppsBorderSpacePx.y;
1056             if (allAppsCellHeightPx < cellContentHeight) {
1057                 // Increase allAppsCellHeight to fit its content.
1058                 allAppsCellHeightPx = cellContentHeight;
1059             }
1060         } else {
1061             float invIconSizeDp = inv.allAppsIconSize[mTypeIndex];
1062             float invIconTextSizeSp = inv.allAppsIconTextSize[mTypeIndex];
1063             allAppsIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale));
1064             allAppsIconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * scale);
1065             allAppsIconDrawablePaddingPx =
1066                     res.getDimensionPixelSize(R.dimen.all_apps_icon_drawable_padding);
1067             allAppsCellWidthPx = allAppsIconSizePx + (2 * allAppsIconDrawablePaddingPx);
1068         }
1069 
1070         updateAllAppsContainerWidth(res);
1071         if (isVerticalBarLayout()) {
1072             hideWorkspaceLabelsIfNotEnoughSpace();
1073         }
1074     }
1075 
1076     private void updateAvailableFolderCellDimensions(Resources res) {
1077         updateFolderCellSize(1f, res);
1078 
1079         // For usability we can't have the folder use the whole width of the screen
1080         Point totalWorkspacePadding = getTotalWorkspacePadding();
1081 
1082         // Check if the folder fit within the available height.
1083         float contentUsedHeight = folderCellHeightPx * inv.numFolderRows
1084                 + ((inv.numFolderRows - 1) * folderCellLayoutBorderSpacePx)
1085                 + folderFooterHeightPx
1086                 + folderContentPaddingTop;
1087         int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y;
1088         float scaleY = contentMaxHeight / contentUsedHeight;
1089 
1090         // Check if the folder fit within the available width.
1091         float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns
1092                 + ((inv.numFolderColumns - 1) * folderCellLayoutBorderSpacePx)
1093                 + folderContentPaddingLeftRight * 2;
1094         int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x;
1095         float scaleX = contentMaxWidth / contentUsedWidth;
1096 
1097         float scale = Math.min(scaleX, scaleY);
1098         if (scale < 1f) {
1099             updateFolderCellSize(scale, res);
1100         }
1101     }
1102 
1103     private void updateFolderCellSize(float scale, Resources res) {
1104         float invIconSizeDp = inv.iconSize[mTypeIndex];
1105         folderChildIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale));
1106         folderChildTextSizePx = pxFromSp(inv.iconTextSize[mTypeIndex], mMetrics, scale);
1107         folderLabelTextSizePx = Math.max(pxFromSp(MIN_FOLDER_TEXT_SIZE_SP, mMetrics, scale),
1108                 (int) (folderChildTextSizePx * folderLabelTextScale));
1109 
1110         int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
1111 
1112         if (isScalableGrid) {
1113             if (inv.folderStyle == INVALID_RESOURCE_HANDLE) {
1114                 folderCellWidthPx = roundPxValueFromFloat(getCellSize().x * scale);
1115                 folderCellHeightPx = roundPxValueFromFloat(getCellSize().y * scale);
1116             } else {
1117                 folderCellWidthPx = roundPxValueFromFloat(folderCellWidthPx * scale);
1118                 folderCellHeightPx = roundPxValueFromFloat(folderCellHeightPx * scale);
1119             }
1120 
1121             folderContentPaddingTop = roundPxValueFromFloat(folderContentPaddingTop * scale);
1122             folderCellLayoutBorderSpacePx = roundPxValueFromFloat(
1123                     folderCellLayoutBorderSpacePx * scale);
1124             folderFooterHeightPx = roundPxValueFromFloat(folderFooterHeightPx * scale);
1125 
1126             folderContentPaddingLeftRight = folderCellLayoutBorderSpacePx;
1127         } else {
1128             int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding)
1129                     * scale);
1130             int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding)
1131                     * scale);
1132 
1133             folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
1134             folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
1135             folderContentPaddingTop = roundPxValueFromFloat(folderContentPaddingTop * scale);
1136             folderContentPaddingLeftRight =
1137                     res.getDimensionPixelSize(R.dimen.folder_content_padding_left_right);
1138             folderFooterHeightPx =
1139                     roundPxValueFromFloat(
1140                             res.getDimensionPixelSize(R.dimen.folder_footer_height_default)
1141                                     * scale);
1142 
1143         }
1144 
1145         folderChildDrawablePaddingPx = Math.max(0,
1146                 (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
1147     }
1148 
1149     public void updateInsets(Rect insets) {
1150         mInsets.set(insets);
1151     }
1152 
1153     /**
1154      * The current device insets. This is generally same as the insets being dispatched to
1155      * {@link Insettable} elements, but can differ if the element is using a different profile.
1156      */
1157     public Rect getInsets() {
1158         return mInsets;
1159     }
1160 
1161     public Point getCellSize() {
1162         return getCellSize(null);
1163     }
1164 
1165     public Point getCellSize(Point result) {
1166         if (result == null) {
1167             result = new Point();
1168         }
1169 
1170         int shortcutAndWidgetContainerWidth =
1171                 getCellLayoutWidth() - (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right);
1172         result.x = calculateCellWidth(shortcutAndWidgetContainerWidth, cellLayoutBorderSpacePx.x,
1173                 inv.numColumns);
1174         int shortcutAndWidgetContainerHeight =
1175                 getCellLayoutHeight() - (cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom);
1176         result.y = calculateCellHeight(shortcutAndWidgetContainerHeight, cellLayoutBorderSpacePx.y,
1177                 inv.numRows);
1178         return result;
1179     }
1180 
1181     /**
1182      * Returns the left and right space on the cell, which is the cell width - icon size
1183      */
1184     public int getCellHorizontalSpace() {
1185         return getCellSize().x - iconSizePx;
1186     }
1187 
1188     /**
1189      * Gets the number of panels within the workspace.
1190      */
1191     public int getPanelCount() {
1192         return isTwoPanels ? 2 : 1;
1193     }
1194 
1195     /**
1196      * Gets the space in px from the bottom of last item in the vertical-bar hotseat to the
1197      * bottom of the screen.
1198      */
1199     private int getVerticalHotseatLastItemBottomOffset(Context context) {
1200         Rect hotseatBarPadding = getHotseatLayoutPadding(context);
1201         int cellHeight = calculateCellHeight(
1202                 heightPx - hotseatBarPadding.top - hotseatBarPadding.bottom, hotseatBorderSpace,
1203                 numShownHotseatIcons);
1204         int extraIconEndSpacing = (cellHeight - iconSizePx) / 2;
1205         return extraIconEndSpacing + hotseatBarPadding.bottom;
1206     }
1207 
1208     /**
1209      * Gets the scaled top of the workspace in px for the spring-loaded edit state.
1210      */
1211     public float getCellLayoutSpringLoadShrunkTop() {
1212         return mInsets.top + dropTargetBarTopMarginPx + dropTargetBarSizePx
1213                 + dropTargetBarBottomMarginPx;
1214     }
1215 
1216     /**
1217      * Gets the scaled bottom of the workspace in px for the spring-loaded edit state.
1218      */
1219     public float getCellLayoutSpringLoadShrunkBottom(Context context) {
1220         int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx;
1221         return heightPx - (isVerticalBarLayout()
1222                 ? getVerticalHotseatLastItemBottomOffset(context) : topOfHotseat);
1223     }
1224 
1225     /**
1226      * Gets the scale of the workspace for the spring-loaded edit state.
1227      */
1228     public float getWorkspaceSpringLoadScale(Context context) {
1229         float scale =
1230                 (getCellLayoutSpringLoadShrunkBottom(context) - getCellLayoutSpringLoadShrunkTop())
1231                         / getCellLayoutHeight();
1232         scale = Math.min(scale, 1f);
1233 
1234         // Reduce scale if next pages would not be visible after scaling the workspace.
1235         int workspaceWidth = availableWidthPx;
1236         float scaledWorkspaceWidth = workspaceWidth * scale;
1237         float maxAvailableWidth = workspaceWidth - (2 * workspaceSpringLoadedMinNextPageVisiblePx);
1238         if (scaledWorkspaceWidth > maxAvailableWidth) {
1239             scale *= maxAvailableWidth / scaledWorkspaceWidth;
1240         }
1241         return scale;
1242     }
1243 
1244     /**
1245      * Gets the width of a single Cell Layout, aka a single panel within a Workspace.
1246      *
1247      * <p>This is the width of a Workspace, less its horizontal padding. Note that two-panel
1248      * layouts have two Cell Layouts per workspace.
1249      */
1250     public int getCellLayoutWidth() {
1251         return (availableWidthPx - getTotalWorkspacePadding().x) / getPanelCount();
1252     }
1253 
1254     /**
1255      * Gets the height of a single Cell Layout, aka a single panel within a Workspace.
1256      *
1257      * <p>This is the height of a Workspace, less its vertical padding.
1258      */
1259     public int getCellLayoutHeight() {
1260         return availableHeightPx - getTotalWorkspacePadding().y;
1261     }
1262 
1263     public Point getTotalWorkspacePadding() {
1264         return new Point(workspacePadding.left + workspacePadding.right,
1265                 workspacePadding.top + workspacePadding.bottom);
1266     }
1267 
1268     /**
1269      * Updates {@link #workspacePadding} as a result of any internal value change to reflect the
1270      * new workspace padding
1271      */
1272     private void updateWorkspacePadding() {
1273         Rect padding = workspacePadding;
1274         if (isVerticalBarLayout()) {
1275             padding.top = 0;
1276             padding.bottom = edgeMarginPx;
1277             if (isSeascape()) {
1278                 padding.left = hotseatBarSizePx;
1279                 padding.right = hotseatBarSidePaddingStartPx;
1280             } else {
1281                 padding.left = hotseatBarSidePaddingStartPx;
1282                 padding.right = hotseatBarSizePx;
1283             }
1284         } else {
1285             // Pad the bottom of the workspace with hotseat bar
1286             // and leave a bit of space in case a widget go all the way down
1287             int paddingBottom = hotseatBarSizePx + workspaceBottomPadding
1288                     + workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace
1289                     - mInsets.bottom;
1290             int paddingTop = workspaceTopPadding + (isScalableGrid ? 0 : edgeMarginPx);
1291             int paddingSide = desiredWorkspaceHorizontalMarginPx;
1292 
1293             padding.set(paddingSide, paddingTop, paddingSide, paddingBottom);
1294         }
1295         insetPadding(workspacePadding, cellLayoutPaddingPx);
1296     }
1297 
1298     private void insetPadding(Rect paddings, Rect insets) {
1299         insets.left = Math.min(insets.left, paddings.left);
1300         paddings.left -= insets.left;
1301 
1302         insets.top = Math.min(insets.top, paddings.top);
1303         paddings.top -= insets.top;
1304 
1305         insets.right = Math.min(insets.right, paddings.right);
1306         paddings.right -= insets.right;
1307 
1308         insets.bottom = Math.min(insets.bottom, paddings.bottom);
1309         paddings.bottom -= insets.bottom;
1310     }
1311 
1312     /**
1313      * Returns the padding for hotseat view
1314      */
1315     public Rect getHotseatLayoutPadding(Context context) {
1316         Rect hotseatBarPadding = new Rect();
1317         if (isVerticalBarLayout()) {
1318             // The hotseat icons will be placed in the middle of the hotseat cells.
1319             // Changing the hotseatCellHeightPx is not affecting hotseat icon positions
1320             // in vertical bar layout.
1321             // Workspace icons are moved up by a small factor. The variable diffOverlapFactor
1322             // is set to account for that difference.
1323             float diffOverlapFactor = iconSizePx * (ICON_OVERLAP_FACTOR - 1) / 2;
1324             int paddingTop = Math.max((int) (mInsets.top + cellLayoutPaddingPx.top
1325                     - diffOverlapFactor), 0);
1326             int paddingBottom = Math.max((int) (mInsets.bottom + cellLayoutPaddingPx.bottom
1327                     + diffOverlapFactor), 0);
1328 
1329             if (isSeascape()) {
1330                 hotseatBarPadding.set(mInsets.left + hotseatBarSidePaddingStartPx, paddingTop,
1331                         hotseatBarSidePaddingEndPx, paddingBottom);
1332             } else {
1333                 hotseatBarPadding.set(hotseatBarSidePaddingEndPx, paddingTop,
1334                         mInsets.right + hotseatBarSidePaddingStartPx, paddingBottom);
1335             }
1336         } else if (isTaskbarPresent) {
1337             // Center the QSB vertically with hotseat
1338             int hotseatBarBottomPadding = getHotseatBarBottomPadding();
1339             int hotseatBarTopPadding =
1340                     hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx;
1341 
1342             int hotseatWidth = getHotseatRequiredWidth();
1343             int startSpacing;
1344             int endSpacing;
1345             // Hotseat aligns to the left with nav buttons
1346             if (hotseatBarEndOffset > 0) {
1347                 startSpacing = inlineNavButtonsEndSpacingPx;
1348                 endSpacing = availableWidthPx - hotseatWidth - startSpacing + hotseatBorderSpace;
1349             } else {
1350                 startSpacing = (availableWidthPx - hotseatWidth) / 2;
1351                 endSpacing = startSpacing;
1352             }
1353             startSpacing += getAdditionalQsbSpace();
1354 
1355             hotseatBarPadding.top = hotseatBarTopPadding;
1356             hotseatBarPadding.bottom = hotseatBarBottomPadding;
1357             boolean isRtl = Utilities.isRtl(context.getResources());
1358             if (isRtl) {
1359                 hotseatBarPadding.left = endSpacing;
1360                 hotseatBarPadding.right = startSpacing;
1361             } else {
1362                 hotseatBarPadding.left = startSpacing;
1363                 hotseatBarPadding.right = endSpacing;
1364             }
1365 
1366         } else if (isScalableGrid) {
1367             int sideSpacing = (availableWidthPx - hotseatQsbWidth) / 2;
1368             hotseatBarPadding.set(sideSpacing,
1369                     0,
1370                     sideSpacing,
1371                     getHotseatBarBottomPadding());
1372         } else {
1373             // We want the edges of the hotseat to line up with the edges of the workspace, but the
1374             // icons in the hotseat are a different size, and so don't line up perfectly. To account
1375             // for this, we pad the left and right of the hotseat with half of the difference of a
1376             // workspace cell vs a hotseat cell.
1377             float workspaceCellWidth = (float) widthPx / inv.numColumns;
1378             float hotseatCellWidth = (float) widthPx / numShownHotseatIcons;
1379             int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
1380             hotseatBarPadding.set(
1381                     hotseatAdjustment + workspacePadding.left + cellLayoutPaddingPx.left
1382                             + mInsets.left,
1383                     0,
1384                     hotseatAdjustment + workspacePadding.right + cellLayoutPaddingPx.right
1385                             + mInsets.right,
1386                     getHotseatBarBottomPadding());
1387         }
1388         return hotseatBarPadding;
1389     }
1390 
1391     private int getAdditionalQsbSpace() {
1392         return isQsbInline ? hotseatQsbWidth + hotseatBorderSpace : 0;
1393     }
1394 
1395     /**
1396      * Calculate how much space the hotseat needs to be shown completely
1397      */
1398     private int getHotseatRequiredWidth() {
1399         int additionalQsbSpace = getAdditionalQsbSpace();
1400         return iconSizePx * numShownHotseatIcons
1401                 + hotseatBorderSpace * (numShownHotseatIcons - (areNavButtonsInline ? 0 : 1))
1402                 + additionalQsbSpace;
1403     }
1404 
1405     /**
1406      * Returns the number of pixels the QSB is translated from the bottom of the screen.
1407      */
1408     public int getQsbOffsetY() {
1409         if (isQsbInline) {
1410             return getHotseatBarBottomPadding() - ((hotseatQsbHeight - hotseatCellHeightPx) / 2);
1411         } else if (isTaskbarPresent) { // QSB on top
1412             return hotseatBarSizePx - hotseatQsbHeight + hotseatQsbShadowHeight;
1413         } else {
1414             return hotseatBarBottomSpacePx - hotseatQsbShadowHeight;
1415         }
1416     }
1417 
1418     /**
1419      * Returns the number of pixels the hotseat is translated from the bottom of the screen.
1420      */
1421     private int getHotseatBarBottomPadding() {
1422         if (isTaskbarPresent) { // QSB on top or inline
1423             return hotseatBarBottomSpacePx - (Math.abs(hotseatCellHeightPx - iconSizePx) / 2);
1424         } else {
1425             return hotseatBarSizePx - hotseatCellHeightPx;
1426         }
1427     }
1428 
1429     /**
1430      * Returns the number of pixels the taskbar is translated from the bottom of the screen.
1431      */
1432     public int getTaskbarOffsetY() {
1433         int taskbarIconBottomSpace = (taskbarHeight - iconSizePx) / 2;
1434         int launcherIconBottomSpace =
1435                 Math.min((hotseatCellHeightPx - iconSizePx) / 2, gridVisualizationPaddingY);
1436         return getHotseatBarBottomPadding() + launcherIconBottomSpace - taskbarIconBottomSpace;
1437     }
1438 
1439     /**
1440      * Returns the number of pixels required below OverviewActions excluding insets.
1441      */
1442     public int getOverviewActionsClaimedSpaceBelow() {
1443         if (isTaskbarPresent) {
1444             return taskbarHeight + taskbarBottomMargin * 2;
1445         }
1446         return mInsets.bottom;
1447     }
1448 
1449     /** Gets the space that the overview actions will take, including bottom margin. */
1450     public int getOverviewActionsClaimedSpace() {
1451         int overviewActionsSpace = isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()
1452                 ? 0
1453                 : (overviewActionsTopMarginPx + overviewActionsHeight);
1454         return overviewActionsSpace + getOverviewActionsClaimedSpaceBelow();
1455     }
1456 
1457     /**
1458      * Takes the View and return the scales of width and height depending on the DeviceProfile
1459      * specifications
1460      *
1461      * @param itemInfo The tag of the widget view
1462      * @return A PointF instance with the x set to be the scale of width, and y being the scale of
1463      * height
1464      */
1465     @NonNull
1466     public PointF getAppWidgetScale(@Nullable final ItemInfo itemInfo) {
1467         return mViewScaleProvider.getScaleFromItemInfo(itemInfo);
1468     }
1469 
1470     /**
1471      * @return the bounds for which the open folders should be contained within
1472      */
1473     public Rect getAbsoluteOpenFolderBounds() {
1474         if (isVerticalBarLayout()) {
1475             // Folders should only appear right of the drop target bar and left of the hotseat
1476             return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx,
1477                     mInsets.top,
1478                     mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx,
1479                     mInsets.top + availableHeightPx);
1480         } else {
1481             // Folders should only appear below the drop target bar and above the hotseat
1482             int hotseatTop = isTaskbarPresent ? taskbarHeight : hotseatBarSizePx;
1483             return new Rect(mInsets.left + edgeMarginPx,
1484                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
1485                     mInsets.left + availableWidthPx - edgeMarginPx,
1486                     mInsets.top + availableHeightPx - hotseatTop
1487                             - workspacePageIndicatorHeight - edgeMarginPx);
1488         }
1489     }
1490 
1491     public static int calculateCellWidth(int width, int borderSpacing, int countX) {
1492         return (width - ((countX - 1) * borderSpacing)) / countX;
1493     }
1494 
1495     public static int calculateCellHeight(int height, int borderSpacing, int countY) {
1496         return (height - ((countY - 1) * borderSpacing)) / countY;
1497     }
1498 
1499     /**
1500      * When {@code true}, the device is in landscape mode and the hotseat is on the right column.
1501      * When {@code false}, either device is in portrait mode or the device is in landscape mode and
1502      * the hotseat is on the bottom row.
1503      */
1504     public boolean isVerticalBarLayout() {
1505         return isLandscape && transposeLayoutWithOrientation;
1506     }
1507 
1508     /**
1509      * Updates orientation information and returns true if it has changed from the previous value.
1510      */
1511     public boolean updateIsSeascape(Context context) {
1512         if (isVerticalBarLayout()) {
1513             boolean isSeascape = DisplayController.INSTANCE.get(context)
1514                     .getInfo().rotation == Surface.ROTATION_270;
1515             if (mIsSeascape != isSeascape) {
1516                 mIsSeascape = isSeascape;
1517                 // Hotseat changing sides requires updating workspace left/right paddings
1518                 updateWorkspacePadding();
1519                 return true;
1520             }
1521         }
1522         return false;
1523     }
1524 
1525     public boolean isSeascape() {
1526         return isVerticalBarLayout() && mIsSeascape;
1527     }
1528 
1529     public boolean shouldFadeAdjacentWorkspaceScreens() {
1530         return isVerticalBarLayout();
1531     }
1532 
1533     public int getCellContentHeight(@ContainerType int containerType) {
1534         switch (containerType) {
1535             case CellLayout.WORKSPACE:
1536                 return cellHeightPx;
1537             case CellLayout.FOLDER:
1538                 return folderCellHeightPx;
1539             case CellLayout.HOTSEAT:
1540                 // The hotseat is the only container where the cell height is going to be
1541                 // different from the content within that cell.
1542                 return iconSizePx;
1543             default:
1544                 // ??
1545                 return 0;
1546         }
1547     }
1548 
1549     private String pxToDpStr(String name, float value) {
1550         return "\t" + name + ": " + value + "px (" + dpiFromPx(value, mMetrics.densityDpi) + "dp)";
1551     }
1552 
1553     private String dpPointFToString(String name, PointF value) {
1554         return String.format(Locale.ENGLISH, "\t%s: PointF(%.1f, %.1f)dp", name, value.x, value.y);
1555     }
1556 
1557     /** Dumps various DeviceProfile variables to the specified writer. */
1558     public void dump(Context context, String prefix, PrintWriter writer) {
1559         writer.println(prefix + "DeviceProfile:");
1560         writer.println(prefix + "\t1 dp = " + mMetrics.density + " px");
1561 
1562         writer.println(prefix + "\tisTablet:" + isTablet);
1563         writer.println(prefix + "\tisPhone:" + isPhone);
1564         writer.println(prefix + "\ttransposeLayoutWithOrientation:"
1565                 + transposeLayoutWithOrientation);
1566         writer.println(prefix + "\tisGestureMode:" + isGestureMode);
1567 
1568         writer.println(prefix + "\tisLandscape:" + isLandscape);
1569         writer.println(prefix + "\tisMultiWindowMode:" + isMultiWindowMode);
1570         writer.println(prefix + "\tisTwoPanels:" + isTwoPanels);
1571 
1572         writer.println(prefix + pxToDpStr("windowX", windowX));
1573         writer.println(prefix + pxToDpStr("windowY", windowY));
1574         writer.println(prefix + pxToDpStr("widthPx", widthPx));
1575         writer.println(prefix + pxToDpStr("heightPx", heightPx));
1576         writer.println(prefix + pxToDpStr("availableWidthPx", availableWidthPx));
1577         writer.println(prefix + pxToDpStr("availableHeightPx", availableHeightPx));
1578         writer.println(prefix + pxToDpStr("mInsets.left", mInsets.left));
1579         writer.println(prefix + pxToDpStr("mInsets.top", mInsets.top));
1580         writer.println(prefix + pxToDpStr("mInsets.right", mInsets.right));
1581         writer.println(prefix + pxToDpStr("mInsets.bottom", mInsets.bottom));
1582 
1583         writer.println(prefix + "\taspectRatio:" + aspectRatio);
1584 
1585         writer.println(prefix + "\tisScalableGrid:" + isScalableGrid);
1586 
1587         writer.println(prefix + "\tinv.numRows: " + inv.numRows);
1588         writer.println(prefix + "\tinv.numColumns: " + inv.numColumns);
1589         writer.println(prefix + "\tinv.numSearchContainerColumns: "
1590                 + inv.numSearchContainerColumns);
1591 
1592         writer.println(prefix + dpPointFToString("minCellSize", inv.minCellSize[mTypeIndex]));
1593 
1594         writer.println(prefix + pxToDpStr("cellWidthPx", cellWidthPx));
1595         writer.println(prefix + pxToDpStr("cellHeightPx", cellHeightPx));
1596 
1597         writer.println(prefix + pxToDpStr("getCellSize().x", getCellSize().x));
1598         writer.println(prefix + pxToDpStr("getCellSize().y", getCellSize().y));
1599 
1600         writer.println(prefix + pxToDpStr("cellLayoutBorderSpacePx Horizontal",
1601                 cellLayoutBorderSpacePx.x));
1602         writer.println(prefix + pxToDpStr("cellLayoutBorderSpacePx Vertical",
1603                 cellLayoutBorderSpacePx.y));
1604         writer.println(
1605                 prefix + pxToDpStr("cellLayoutPaddingPx.left", cellLayoutPaddingPx.left));
1606         writer.println(
1607                 prefix + pxToDpStr("cellLayoutPaddingPx.top", cellLayoutPaddingPx.top));
1608         writer.println(
1609                 prefix + pxToDpStr("cellLayoutPaddingPx.right", cellLayoutPaddingPx.right));
1610         writer.println(
1611                 prefix + pxToDpStr("cellLayoutPaddingPx.bottom", cellLayoutPaddingPx.bottom));
1612 
1613         writer.println(prefix + pxToDpStr("iconSizePx", iconSizePx));
1614         writer.println(prefix + pxToDpStr("iconTextSizePx", iconTextSizePx));
1615         writer.println(prefix + pxToDpStr("iconDrawablePaddingPx", iconDrawablePaddingPx));
1616 
1617         writer.println(prefix + "\tinv.numFolderRows: " + inv.numFolderRows);
1618         writer.println(prefix + "\tinv.numFolderColumns: " + inv.numFolderColumns);
1619         writer.println(prefix + pxToDpStr("folderCellWidthPx", folderCellWidthPx));
1620         writer.println(prefix + pxToDpStr("folderCellHeightPx", folderCellHeightPx));
1621         writer.println(prefix + pxToDpStr("folderChildIconSizePx", folderChildIconSizePx));
1622         writer.println(prefix + pxToDpStr("folderChildTextSizePx", folderChildTextSizePx));
1623         writer.println(prefix + pxToDpStr("folderChildDrawablePaddingPx",
1624                 folderChildDrawablePaddingPx));
1625         writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx",
1626                 folderCellLayoutBorderSpacePx));
1627         writer.println(prefix + pxToDpStr("folderContentPaddingLeftRight",
1628                 folderContentPaddingLeftRight));
1629         writer.println(prefix + pxToDpStr("folderTopPadding", folderContentPaddingTop));
1630         writer.println(prefix + pxToDpStr("folderFooterHeight", folderFooterHeightPx));
1631 
1632         writer.println(prefix + pxToDpStr("bottomSheetTopPadding", bottomSheetTopPadding));
1633         writer.println(prefix + "\tbottomSheetOpenDuration: " + bottomSheetOpenDuration);
1634         writer.println(prefix + "\tbottomSheetCloseDuration: " + bottomSheetCloseDuration);
1635         writer.println(prefix + "\tbottomSheetWorkspaceScale: " + bottomSheetWorkspaceScale);
1636         writer.println(prefix + "\tbottomSheetDepth: " + bottomSheetDepth);
1637 
1638         writer.println(prefix + pxToDpStr("allAppsShiftRange", allAppsShiftRange));
1639         writer.println(prefix + pxToDpStr("allAppsTopPadding", allAppsTopPadding));
1640         writer.println(prefix + "\tallAppsOpenDuration: " + allAppsOpenDuration);
1641         writer.println(prefix + "\tallAppsCloseDuration: " + allAppsCloseDuration);
1642         writer.println(prefix + pxToDpStr("allAppsIconSizePx", allAppsIconSizePx));
1643         writer.println(prefix + pxToDpStr("allAppsIconTextSizePx", allAppsIconTextSizePx));
1644         writer.println(prefix + pxToDpStr("allAppsIconDrawablePaddingPx",
1645                 allAppsIconDrawablePaddingPx));
1646         writer.println(prefix + pxToDpStr("allAppsCellHeightPx", allAppsCellHeightPx));
1647         writer.println(prefix + pxToDpStr("allAppsCellWidthPx", allAppsCellWidthPx));
1648         writer.println(prefix + pxToDpStr("allAppsBorderSpacePxX", allAppsBorderSpacePx.x));
1649         writer.println(prefix + pxToDpStr("allAppsBorderSpacePxY", allAppsBorderSpacePx.y));
1650         writer.println(prefix + "\tnumShownAllAppsColumns: " + numShownAllAppsColumns);
1651         writer.println(prefix + pxToDpStr("allAppsLeftRightPadding", allAppsLeftRightPadding));
1652         writer.println(prefix + pxToDpStr("allAppsLeftRightMargin", allAppsLeftRightMargin));
1653 
1654         writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx));
1655         writer.println(prefix + "\tinv.hotseatColumnSpan: " + inv.hotseatColumnSpan[mTypeIndex]);
1656         writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx));
1657         writer.println(prefix + pxToDpStr("hotseatBarBottomSpacePx", hotseatBarBottomSpacePx));
1658         writer.println(prefix + pxToDpStr("hotseatBarSidePaddingStartPx",
1659                 hotseatBarSidePaddingStartPx));
1660         writer.println(prefix + pxToDpStr("hotseatBarSidePaddingEndPx",
1661                 hotseatBarSidePaddingEndPx));
1662         writer.println(prefix + pxToDpStr("hotseatBarEndOffset", hotseatBarEndOffset));
1663         writer.println(prefix + pxToDpStr("hotseatQsbSpace", hotseatQsbSpace));
1664         writer.println(prefix + pxToDpStr("hotseatQsbHeight", hotseatQsbHeight));
1665         writer.println(prefix + pxToDpStr("springLoadedHotseatBarTopMarginPx",
1666                 springLoadedHotseatBarTopMarginPx));
1667         Rect hotseatLayoutPadding = getHotseatLayoutPadding(context);
1668         writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).top",
1669                 hotseatLayoutPadding.top));
1670         writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).bottom",
1671                 hotseatLayoutPadding.bottom));
1672         writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).left",
1673                 hotseatLayoutPadding.left));
1674         writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).right",
1675                 hotseatLayoutPadding.right));
1676         writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons);
1677         writer.println(prefix + pxToDpStr("hotseatBorderSpace", hotseatBorderSpace));
1678         writer.println(prefix + "\tisQsbInline: " + isQsbInline);
1679         writer.println(prefix + pxToDpStr("hotseatQsbWidth", hotseatQsbWidth));
1680 
1681         writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent);
1682         writer.println(prefix + "\tisTaskbarPresentInApps:" + isTaskbarPresentInApps);
1683         writer.println(prefix + pxToDpStr("taskbarHeight", taskbarHeight));
1684         writer.println(prefix + pxToDpStr("stashedTaskbarHeight", stashedTaskbarHeight));
1685         writer.println(prefix + pxToDpStr("taskbarBottomMargin", taskbarBottomMargin));
1686         writer.println(prefix + pxToDpStr("taskbarIconSize", taskbarIconSize));
1687 
1688         writer.println(prefix + pxToDpStr("desiredWorkspaceHorizontalMarginPx",
1689                 desiredWorkspaceHorizontalMarginPx));
1690         writer.println(prefix + pxToDpStr("workspacePadding.left", workspacePadding.left));
1691         writer.println(prefix + pxToDpStr("workspacePadding.top", workspacePadding.top));
1692         writer.println(prefix + pxToDpStr("workspacePadding.right", workspacePadding.right));
1693         writer.println(prefix + pxToDpStr("workspacePadding.bottom", workspacePadding.bottom));
1694 
1695         writer.println(prefix + pxToDpStr("iconScale", iconScale));
1696         writer.println(prefix + pxToDpStr("cellScaleToFit ", cellScaleToFit));
1697         writer.println(prefix + pxToDpStr("extraSpace", extraSpace));
1698         writer.println(prefix + pxToDpStr("unscaled extraSpace", extraSpace / iconScale));
1699 
1700         writer.println(prefix + pxToDpStr("maxEmptySpace", maxEmptySpace));
1701         writer.println(prefix + pxToDpStr("workspaceTopPadding", workspaceTopPadding));
1702         writer.println(prefix + pxToDpStr("workspaceBottomPadding", workspaceBottomPadding));
1703 
1704         writer.println(prefix + pxToDpStr("overviewTaskMarginPx", overviewTaskMarginPx));
1705         writer.println(prefix + pxToDpStr("overviewTaskIconSizePx", overviewTaskIconSizePx));
1706         writer.println(prefix + pxToDpStr("overviewTaskIconDrawableSizePx",
1707                 overviewTaskIconDrawableSizePx));
1708         writer.println(prefix + pxToDpStr("overviewTaskIconDrawableSizeGridPx",
1709                 overviewTaskIconDrawableSizeGridPx));
1710         writer.println(prefix + pxToDpStr("overviewTaskThumbnailTopMarginPx",
1711                 overviewTaskThumbnailTopMarginPx));
1712         writer.println(prefix + pxToDpStr("overviewActionsTopMarginPx",
1713                 overviewActionsTopMarginPx));
1714         writer.println(prefix + pxToDpStr("overviewActionsHeight",
1715                 overviewActionsHeight));
1716         writer.println(prefix + pxToDpStr("overviewActionsClaimedSpaceBelow",
1717                 getOverviewActionsClaimedSpaceBelow()));
1718         writer.println(prefix + pxToDpStr("overviewActionsButtonSpacing",
1719                 overviewActionsButtonSpacing));
1720         writer.println(prefix + pxToDpStr("overviewPageSpacing", overviewPageSpacing));
1721         writer.println(prefix + pxToDpStr("overviewRowSpacing", overviewRowSpacing));
1722         writer.println(prefix + pxToDpStr("overviewGridSideMargin", overviewGridSideMargin));
1723 
1724         writer.println(prefix + pxToDpStr("dropTargetBarTopMarginPx", dropTargetBarTopMarginPx));
1725         writer.println(prefix + pxToDpStr("dropTargetBarSizePx", dropTargetBarSizePx));
1726         writer.println(
1727                 prefix + pxToDpStr("dropTargetBarBottomMarginPx", dropTargetBarBottomMarginPx));
1728 
1729         writer.println(prefix + pxToDpStr("getCellLayoutSpringLoadShrunkTop()",
1730                 getCellLayoutSpringLoadShrunkTop()));
1731         writer.println(prefix + pxToDpStr("getCellLayoutSpringLoadShrunkBottom()",
1732                 getCellLayoutSpringLoadShrunkBottom(context)));
1733         writer.println(prefix + pxToDpStr("workspaceSpringLoadedMinNextPageVisiblePx",
1734                 workspaceSpringLoadedMinNextPageVisiblePx));
1735         writer.println(prefix + pxToDpStr("getWorkspaceSpringLoadScale()",
1736                 getWorkspaceSpringLoadScale(context)));
1737         writer.println(prefix + pxToDpStr("getCellLayoutHeight()", getCellLayoutHeight()));
1738         writer.println(prefix + pxToDpStr("getCellLayoutWidth()", getCellLayoutWidth()));
1739     }
1740 
1741     /** Returns a reduced representation of this DeviceProfile. */
1742     public String toSmallString() {
1743         return "isTablet:" + isTablet + ", "
1744                 + "isMultiDisplay:" + isMultiDisplay + ", "
1745                 + "widthPx:" + widthPx + ", "
1746                 + "heightPx:" + heightPx + ", "
1747                 + "insets:" + mInsets + ", "
1748                 + "rotationHint:" + rotationHint;
1749     }
1750 
1751     private static Context getContext(Context c, Info info, int orientation, WindowBounds bounds) {
1752         Configuration config = new Configuration(c.getResources().getConfiguration());
1753         config.orientation = orientation;
1754         config.densityDpi = info.getDensityDpi();
1755         config.smallestScreenWidthDp = (int) info.smallestSizeDp(bounds);
1756         return c.createConfigurationContext(config);
1757     }
1758 
1759     /**
1760      * Callback when a component changes the DeviceProfile associated with it, as a result of
1761      * configuration change
1762      */
1763     public interface OnDeviceProfileChangeListener {
1764 
1765         /**
1766          * Called when the device profile is reassigned. Note that for layout and measurements, it
1767          * is sufficient to listen for inset changes. Use this callback when you need to perform
1768          * a one time operation.
1769          */
1770         void onDeviceProfileChanged(DeviceProfile dp);
1771     }
1772 
1773     /**
1774      * Handler that deals with ItemInfo of the views for the DeviceProfile
1775      */
1776     @FunctionalInterface
1777     public interface ViewScaleProvider {
1778         /**
1779          * Get the scales from the view
1780          *
1781          * @param itemInfo The tag of the widget view
1782          * @return PointF instance containing the scale information, or null if using the default
1783          * app widget scale of this device profile.
1784          */
1785         @NonNull
1786         PointF getScaleFromItemInfo(@Nullable ItemInfo itemInfo);
1787     }
1788 
1789     public static class Builder {
1790         private Context mContext;
1791         private InvariantDeviceProfile mInv;
1792         private Info mInfo;
1793 
1794         private WindowBounds mWindowBounds;
1795         private boolean mIsMultiDisplay;
1796 
1797         private boolean mIsMultiWindowMode = false;
1798         private Boolean mTransposeLayoutWithOrientation;
1799         private Boolean mIsGestureMode;
1800         private ViewScaleProvider mViewScaleProvider = null;
1801 
1802         private SparseArray<DotRenderer> mDotRendererCache;
1803 
1804         private Consumer<DeviceProfile> mOverrideProvider;
1805 
1806         public Builder(Context context, InvariantDeviceProfile inv, Info info) {
1807             mContext = context;
1808             mInv = inv;
1809             mInfo = info;
1810         }
1811 
1812         public Builder setMultiWindowMode(boolean isMultiWindowMode) {
1813             mIsMultiWindowMode = isMultiWindowMode;
1814             return this;
1815         }
1816 
1817         public Builder setIsMultiDisplay(boolean isMultiDisplay) {
1818             mIsMultiDisplay = isMultiDisplay;
1819             return this;
1820         }
1821 
1822         public Builder setDotRendererCache(SparseArray<DotRenderer> dotRendererCache) {
1823             mDotRendererCache = dotRendererCache;
1824             return this;
1825         }
1826 
1827         public Builder setWindowBounds(WindowBounds bounds) {
1828             mWindowBounds = bounds;
1829             return this;
1830         }
1831 
1832         public Builder setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation) {
1833             mTransposeLayoutWithOrientation = transposeLayoutWithOrientation;
1834             return this;
1835         }
1836 
1837         public Builder setGestureMode(boolean isGestureMode) {
1838             mIsGestureMode = isGestureMode;
1839             return this;
1840         }
1841 
1842         public Builder withDimensionsOverride(Consumer<DeviceProfile> overrideProvider) {
1843             mOverrideProvider = overrideProvider;
1844             return this;
1845         }
1846 
1847         /**
1848          * Set the viewScaleProvider for the builder
1849          *
1850          * @param viewScaleProvider The viewScaleProvider to be set for the
1851          *                          DeviceProfile
1852          * @return This builder
1853          */
1854         @NonNull
1855         public Builder setViewScaleProvider(@Nullable ViewScaleProvider viewScaleProvider) {
1856             mViewScaleProvider = viewScaleProvider;
1857             return this;
1858         }
1859 
1860         public DeviceProfile build() {
1861             if (mWindowBounds == null) {
1862                 throw new IllegalArgumentException("Window bounds not set");
1863             }
1864             if (mTransposeLayoutWithOrientation == null) {
1865                 mTransposeLayoutWithOrientation = !mInfo.isTablet(mWindowBounds);
1866             }
1867             if (mIsGestureMode == null) {
1868                 mIsGestureMode = mInfo.navigationMode.hasGestures;
1869             }
1870             if (mDotRendererCache == null) {
1871                 mDotRendererCache = new SparseArray<>();
1872             }
1873             if (mViewScaleProvider == null) {
1874                 mViewScaleProvider = DEFAULT_PROVIDER;
1875             }
1876             if (mOverrideProvider == null) {
1877                 mOverrideProvider = DEFAULT_DIMENSION_PROVIDER;
1878             }
1879             return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, mDotRendererCache,
1880                     mIsMultiWindowMode, mTransposeLayoutWithOrientation, mIsMultiDisplay,
1881                     mIsGestureMode, mViewScaleProvider, mOverrideProvider);
1882         }
1883     }
1884 
1885 }
1886