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.app.animation.Interpolators.LINEAR;
20 import static com.android.launcher3.Flags.enableOverviewIconMenu;
21 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
22 import static com.android.launcher3.InvariantDeviceProfile.INDEX_DEFAULT;
23 import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE;
24 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
25 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
26 import static com.android.launcher3.Utilities.dpiFromPx;
27 import static com.android.launcher3.Utilities.pxFromSp;
28 import static com.android.launcher3.config.FeatureFlags.ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH;
29 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
30 import static com.android.launcher3.icons.GraphicsUtils.getShapePath;
31 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
32 import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
33 import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
34 import static com.android.launcher3.testing.shared.ResourceUtils.roundPxValueFromFloat;
35 import static com.android.wm.shell.Flags.enableTinyTaskbar;
36 
37 import android.annotation.SuppressLint;
38 import android.content.Context;
39 import android.content.res.Configuration;
40 import android.content.res.Resources;
41 import android.content.res.TypedArray;
42 import android.graphics.Point;
43 import android.graphics.PointF;
44 import android.graphics.Rect;
45 import android.util.DisplayMetrics;
46 import android.util.SparseArray;
47 import android.view.Surface;
48 
49 import androidx.annotation.NonNull;
50 import androidx.annotation.Nullable;
51 import androidx.annotation.VisibleForTesting;
52 import androidx.core.content.res.ResourcesCompat;
53 
54 import com.android.launcher3.CellLayout.ContainerType;
55 import com.android.launcher3.DevicePaddings.DevicePadding;
56 import com.android.launcher3.icons.DotRenderer;
57 import com.android.launcher3.icons.IconNormalizer;
58 import com.android.launcher3.model.data.ItemInfo;
59 import com.android.launcher3.responsive.CalculatedCellSpec;
60 import com.android.launcher3.responsive.CalculatedHotseatSpec;
61 import com.android.launcher3.responsive.CalculatedResponsiveSpec;
62 import com.android.launcher3.responsive.HotseatSpecsProvider;
63 import com.android.launcher3.responsive.ResponsiveCellSpecsProvider;
64 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType;
65 import com.android.launcher3.responsive.ResponsiveSpec.DimensionType;
66 import com.android.launcher3.responsive.ResponsiveSpecsProvider;
67 import com.android.launcher3.util.CellContentDimensions;
68 import com.android.launcher3.util.DisplayController;
69 import com.android.launcher3.util.DisplayController.Info;
70 import com.android.launcher3.util.IconSizeSteps;
71 import com.android.launcher3.util.ResourceHelper;
72 import com.android.launcher3.util.WindowBounds;
73 import com.android.launcher3.util.window.WindowManagerProxy;
74 
75 import java.io.PrintWriter;
76 import java.util.Locale;
77 import java.util.function.Consumer;
78 
79 @SuppressLint("NewApi")
80 public class DeviceProfile {
81 
82     private static final int DEFAULT_DOT_SIZE = 100;
83     private static final float MIN_FOLDER_TEXT_SIZE_SP = 16f;
84     private static final float MIN_WIDGET_PADDING_DP = 6f;
85 
86     // Minimum aspect ratio beyond which an extra top padding may be applied to a bottom sheet.
87     private static final float MIN_ASPECT_RATIO_FOR_EXTRA_TOP_PADDING = 1.5f;
88     private static final float MAX_ASPECT_RATIO_FOR_ALTERNATE_EDIT_STATE = 1.5f;
89 
90     public static final PointF DEFAULT_SCALE = new PointF(1.0f, 1.0f);
91     public static final ViewScaleProvider DEFAULT_PROVIDER = itemInfo -> DEFAULT_SCALE;
92     public static final Consumer<DeviceProfile> DEFAULT_DIMENSION_PROVIDER = dp -> {
93     };
94 
95     public final InvariantDeviceProfile inv;
96     private final Info mInfo;
97     private final DisplayMetrics mMetrics;
98     private final IconSizeSteps mIconSizeSteps;
99 
100     // Device properties
101     public final boolean isTablet;
102     public final boolean isPhone;
103     public final boolean transposeLayoutWithOrientation;
104     public final boolean isMultiDisplay;
105     public final boolean isTwoPanels;
106     public boolean isPredictiveBackSwipe;
107     public final boolean isQsbInline;
108 
109     // Device properties in current orientation
110     public final boolean isLandscape;
111     public final boolean isMultiWindowMode;
112     public final boolean isGestureMode;
113 
114     public final boolean isLeftRightSplit;
115 
116     public final int windowX;
117     public final int windowY;
118     public final int widthPx;
119     public final int heightPx;
120     public final int availableWidthPx;
121     public final int availableHeightPx;
122     public final int rotationHint;
123 
124     public final float aspectRatio;
125 
126     private final boolean mIsScalableGrid;
127     private final int mTypeIndex;
128 
129     // Responsive grid
130     private final boolean mIsResponsiveGrid;
131     private CalculatedResponsiveSpec mResponsiveWorkspaceWidthSpec;
132     private CalculatedResponsiveSpec mResponsiveWorkspaceHeightSpec;
133     private CalculatedResponsiveSpec mResponsiveAllAppsWidthSpec;
134     private CalculatedResponsiveSpec mResponsiveAllAppsHeightSpec;
135     private CalculatedResponsiveSpec mResponsiveFolderWidthSpec;
136     private CalculatedResponsiveSpec mResponsiveFolderHeightSpec;
137     private CalculatedHotseatSpec mResponsiveHotseatSpec;
138     private CalculatedCellSpec mResponsiveWorkspaceCellSpec;
139     private CalculatedCellSpec mResponsiveAllAppsCellSpec;
140 
141     /**
142      * The maximum amount of left/right workspace padding as a percentage of the screen width.
143      * To be clear, this means that up to 7% of the screen width can be used as left padding, and
144      * 7% of the screen width can be used as right padding.
145      */
146     private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f;
147 
148     private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
149     private static final float TALLER_DEVICE_ASPECT_RATIO_THRESHOLD = 2.15f;
150     private static final float TALL_DEVICE_EXTRA_SPACE_THRESHOLD_DP = 252;
151     private static final float TALL_DEVICE_MORE_EXTRA_SPACE_THRESHOLD_DP = 268;
152 
153     // Workspace
154     public final int desiredWorkspaceHorizontalMarginOriginalPx;
155     public int desiredWorkspaceHorizontalMarginPx;
156     public int gridVisualizationPaddingX;
157     public int gridVisualizationPaddingY;
158     public Point cellLayoutBorderSpaceOriginalPx;
159     public Point cellLayoutBorderSpacePx;
160     public Rect cellLayoutPaddingPx = new Rect();
161 
162     public final int edgeMarginPx;
163     public final float workspaceContentScale;
164     public final int workspaceSpringLoadedMinNextPageVisiblePx;
165 
166     private final int extraSpace;
167     private int maxEmptySpace;
168     public int workspaceTopPadding;
169     public int workspaceBottomPadding;
170 
171     // Workspace page indicator
172     public final int workspacePageIndicatorHeight;
173     private final int mWorkspacePageIndicatorOverlapWorkspace;
174 
175     // Workspace icons
176     public float iconScale;
177     public int iconSizePx;
178     public int iconTextSizePx;
179     public int iconDrawablePaddingPx;
180     private int mIconDrawablePaddingOriginalPx;
181     public boolean iconCenterVertically;
182 
183     public float cellScaleToFit;
184     public int cellWidthPx;
185     public int cellHeightPx;
186     public int workspaceCellPaddingXPx;
187 
188     public int cellYPaddingPx = -1;
189 
190     // Folder
191     public final int numFolderRows;
192     public final int numFolderColumns;
193     public final float folderLabelTextScale;
194     public int folderLabelTextSizePx;
195     public int folderFooterHeightPx;
196     public int folderIconSizePx;
197     public int folderIconOffsetYPx;
198 
199     // Folder content
200     public Point folderCellLayoutBorderSpacePx;
201     public int folderContentPaddingLeftRight;
202     public int folderContentPaddingTop;
203 
204     // Folder cell
205     public int folderCellWidthPx;
206     public int folderCellHeightPx;
207 
208     // Folder child
209     public int folderChildIconSizePx;
210     public int folderChildTextSizePx;
211     public int folderChildDrawablePaddingPx;
212 
213     // Hotseat
214     public int numShownHotseatIcons;
215     public int hotseatCellHeightPx;
216     private int mHotseatColumnSpan;
217     private int mHotseatWidthPx; // not used in vertical bar layout
218     public final boolean areNavButtonsInline;
219     // In portrait: size = height, in landscape: size = width
220     public int hotseatBarSizePx;
221     public int hotseatBarBottomSpacePx;
222     public int hotseatBarEndOffset;
223     public int hotseatQsbSpace;
224     public int springLoadedHotseatBarTopMarginPx;
225     // These 2 values are only used for isVerticalBar
226     // Padding between edge of screen and hotseat
227     public final int mHotseatBarEdgePaddingPx;
228     // Space between hotseat and workspace (not used in responsive)
229     public final int mHotseatBarWorkspaceSpacePx;
230     public int hotseatQsbWidth; // only used when isQsbInline
231     public final int hotseatQsbHeight;
232     public final int hotseatQsbVisualHeight;
233     private final int hotseatQsbShadowHeight;
234     public int hotseatBorderSpace;
235     private final int mMinHotseatIconSpacePx;
236     private final int mMinHotseatQsbWidthPx;
237     private final int mMaxHotseatIconSpacePx;
238     public final int inlineNavButtonsEndSpacingPx;
239     // Space required for the bubble bar between the hotseat and the edge of the screen. If there's
240     // not enough space, the hotseat will adjust itself for the bubble bar.
241     private final int mBubbleBarSpaceThresholdPx;
242 
243     // Bottom sheets
244     public int bottomSheetTopPadding;
245     public int bottomSheetOpenDuration;
246     public int bottomSheetCloseDuration;
247     public float bottomSheetWorkspaceScale;
248     public float bottomSheetDepth;
249 
250     // All apps
251     public Point allAppsBorderSpacePx;
252     public int allAppsShiftRange;
253     public Rect allAppsPadding = new Rect();
254     public int allAppsOpenDuration;
255     public int allAppsCloseDuration;
256     public int allAppsCellHeightPx;
257     public int allAppsCellWidthPx;
258     public int allAppsIconSizePx;
259     public int allAppsIconDrawablePaddingPx;
260     public int allAppsLeftRightMargin;
261     public final int numShownAllAppsColumns;
262     public float allAppsIconTextSizePx;
263 
264     // Overview
265     public int overviewTaskMarginPx;
266     public int overviewTaskIconSizePx;
267     public int overviewTaskIconDrawableSizePx;
268     public int overviewTaskIconDrawableSizeGridPx;
269     public int overviewTaskThumbnailTopMarginPx;
270     public final int overviewActionsHeight;
271     public final int overviewActionsTopMarginPx;
272     public final int overviewActionsButtonSpacing;
273     public int overviewPageSpacing;
274     public int overviewRowSpacing;
275     public int overviewGridSideMargin;
276 
277     // Split staging
278     public int splitPlaceholderInset;
279 
280     // Widgets
281     private final ViewScaleProvider mViewScaleProvider;
282 
283     // Drop Target
284     public int dropTargetBarSizePx;
285     public int dropTargetBarTopMarginPx;
286     public int dropTargetBarBottomMarginPx;
287     public int dropTargetDragPaddingPx;
288     public int dropTargetTextSizePx;
289     public int dropTargetHorizontalPaddingPx;
290     public int dropTargetVerticalPaddingPx;
291     public int dropTargetGapPx;
292     public int dropTargetButtonWorkspaceEdgeGapPx;
293 
294     // Insets
295     private final Rect mInsets = new Rect();
296     public final Rect workspacePadding = new Rect();
297     // Additional padding added to the widget inside its cellSpace. It is applied outside
298     // the widgetView, such that the actual view size is same as the widget size.
299     public final Rect widgetPadding = new Rect();
300 
301     // When true, nav bar is on the left side of the screen.
302     private boolean mIsSeascape;
303 
304     // Notification dots
305     public final DotRenderer mDotRendererWorkSpace;
306     public final DotRenderer mDotRendererAllApps;
307 
308     // Taskbar
309     public boolean isTaskbarPresent;
310     // Whether Taskbar will inset the bottom of apps by taskbarSize.
311     public boolean isTaskbarPresentInApps;
312     public final int taskbarHeight;
313     public final int stashedTaskbarHeight;
314     public final int taskbarBottomMargin;
315     public final int taskbarIconSize;
316     private final int mTransientTaskbarClaimedSpace;
317     // If true, used to layout taskbar in 3 button navigation mode.
318     public final boolean startAlignTaskbar;
319     public final boolean isTransientTaskbar;
320     // DragController
321     public int flingToDeleteThresholdVelocity;
322 
323     /** 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, boolean isTransientTaskbar)324     DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
325             SparseArray<DotRenderer> dotRendererCache, boolean isMultiWindowMode,
326             boolean transposeLayoutWithOrientation, boolean isMultiDisplay, boolean isGestureMode,
327             @NonNull final ViewScaleProvider viewScaleProvider,
328             @NonNull final Consumer<DeviceProfile> dimensionOverrideProvider,
329             boolean isTransientTaskbar) {
330 
331         this.inv = inv;
332         this.isLandscape = windowBounds.isLandscape();
333         this.isMultiWindowMode = isMultiWindowMode;
334         this.transposeLayoutWithOrientation = transposeLayoutWithOrientation;
335         this.isMultiDisplay = isMultiDisplay;
336         this.isGestureMode = isGestureMode;
337         windowX = windowBounds.bounds.left;
338         windowY = windowBounds.bounds.top;
339         this.rotationHint = windowBounds.rotationHint;
340         mInsets.set(windowBounds.insets);
341 
342         // TODO(b/241386436): shouldn't change any launcher behaviour
343         mIsResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE
344                 && inv.allAppsSpecsId != INVALID_RESOURCE_HANDLE
345                 && inv.folderSpecsId != INVALID_RESOURCE_HANDLE
346                 && inv.hotseatSpecsId != INVALID_RESOURCE_HANDLE
347                 && inv.workspaceCellSpecsId != INVALID_RESOURCE_HANDLE
348                 && inv.allAppsCellSpecsId != INVALID_RESOURCE_HANDLE;
349 
350         mIsScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
351         // Determine device posture.
352         mInfo = info;
353         isTablet = info.isTablet(windowBounds);
354         isPhone = !isTablet;
355         isTwoPanels = isTablet && isMultiDisplay;
356         isTaskbarPresent = (isTablet || (enableTinyTaskbar() && isGestureMode))
357                 && WindowManagerProxy.INSTANCE.get(context).isTaskbarDrawnInProcess();
358 
359         // Some more constants.
360         context = getContext(context, info, isVerticalBarLayout() || (isTablet && isLandscape)
361                         ? Configuration.ORIENTATION_LANDSCAPE
362                         : Configuration.ORIENTATION_PORTRAIT,
363                 windowBounds);
364         final Resources res = context.getResources();
365         mMetrics = res.getDisplayMetrics();
366 
367         mIconSizeSteps = new IconSizeSteps(res);
368 
369         // Determine sizes.
370         widthPx = windowBounds.bounds.width();
371         heightPx = windowBounds.bounds.height();
372         availableWidthPx = windowBounds.availableSize.x;
373         availableHeightPx = windowBounds.availableSize.y;
374 
375         aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
376         if (isTwoPanels) {
377             if (isLandscape) {
378                 mTypeIndex = INDEX_TWO_PANEL_LANDSCAPE;
379             } else {
380                 mTypeIndex = INDEX_TWO_PANEL_PORTRAIT;
381             }
382         } else {
383             if (isLandscape) {
384                 mTypeIndex = INDEX_LANDSCAPE;
385             } else {
386                 mTypeIndex = INDEX_DEFAULT;
387             }
388         }
389 
390         this.isTransientTaskbar = isTransientTaskbar;
391         int transientTaskbarIconSize = pxFromDp(inv.transientTaskbarIconSize[mTypeIndex], mMetrics);
392         int transientTaskbarBottomMargin =
393                 res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin);
394         int transientTaskbarHeight =
395                 Math.round((transientTaskbarIconSize * ICON_VISIBLE_AREA_FACTOR)
396                         + (2 * res.getDimensionPixelSize(R.dimen.transient_taskbar_padding)));
397         mTransientTaskbarClaimedSpace = transientTaskbarHeight + 2 * transientTaskbarBottomMargin;
398 
399         if (!isTaskbarPresent) {
400             taskbarIconSize = taskbarHeight = stashedTaskbarHeight = taskbarBottomMargin = 0;
401             startAlignTaskbar = false;
402         } else if (isTransientTaskbar) {
403             taskbarIconSize = transientTaskbarIconSize;
404             taskbarHeight = transientTaskbarHeight;
405             stashedTaskbarHeight =
406                     res.getDimensionPixelSize(R.dimen.transient_taskbar_stashed_height);
407             taskbarBottomMargin = transientTaskbarBottomMargin;
408             startAlignTaskbar = false;
409         } else {
410             taskbarIconSize = pxFromDp(ResourcesCompat.getFloat(res, R.dimen.taskbar_icon_size),
411                     mMetrics);
412             taskbarHeight = res.getDimensionPixelSize(R.dimen.taskbar_size);
413             stashedTaskbarHeight = res.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
414             taskbarBottomMargin = 0;
415             startAlignTaskbar = inv.startAlignTaskbar[mTypeIndex];
416         }
417 
418         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
419         workspaceContentScale = res.getFloat(R.dimen.workspace_content_scale);
420 
421         gridVisualizationPaddingX = res.getDimensionPixelSize(
422                 R.dimen.grid_visualization_horizontal_cell_spacing);
423         gridVisualizationPaddingY = res.getDimensionPixelSize(
424                 R.dimen.grid_visualization_vertical_cell_spacing);
425 
426         {
427             // In large screens, in portrait mode, a bottom sheet can appear too elongated, so, we
428             // apply additional padding.
429             final boolean applyExtraTopPadding = isTablet
430                     && !isLandscape
431                     && (aspectRatio > MIN_ASPECT_RATIO_FOR_EXTRA_TOP_PADDING);
432             final int derivedTopPadding = heightPx / 6;
433             bottomSheetTopPadding = mInsets.top // statusbar height
434                     + (applyExtraTopPadding ? derivedTopPadding : 0)
435                     + (isTablet ? 0 : edgeMarginPx); // phones need edgeMarginPx additional padding
436         }
437 
438         bottomSheetOpenDuration = res.getInteger(R.integer.config_bottomSheetOpenDuration);
439         bottomSheetCloseDuration = res.getInteger(R.integer.config_bottomSheetCloseDuration);
440         if (isTablet) {
441             bottomSheetWorkspaceScale = workspaceContentScale;
442             if (isMultiDisplay && !ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH.get()) {
443                 // TODO(b/259893832): Revert to use maxWallpaperScale to calculate bottomSheetDepth
444                 // when screen recorder bug is fixed.
445                 if (enableScalingRevealHomeAnimation()) {
446                     bottomSheetDepth = 0.3f;
447                 } else {
448                     bottomSheetDepth = 1f;
449                 }
450             } else {
451                 // The goal is to set wallpaper to zoom at workspaceContentScale when in AllApps.
452                 // When depth is 0, wallpaper zoom is set to maxWallpaperScale.
453                 // When depth is 1, wallpaper zoom is set to 1.
454                 // For depth to achieve zoom set to maxWallpaperScale * workspaceContentScale:
455                 float maxWallpaperScale = res.getFloat(R.dimen.config_wallpaperMaxScale);
456                 bottomSheetDepth = Utilities.mapToRange(maxWallpaperScale * workspaceContentScale,
457                         maxWallpaperScale, 1f, 0f, 1f, LINEAR);
458             }
459         } else {
460             bottomSheetWorkspaceScale = 1f;
461             bottomSheetDepth = 0f;
462         }
463 
464         folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
465         numFolderRows = inv.numFolderRows[mTypeIndex];
466         numFolderColumns = inv.numFolderColumns[mTypeIndex];
467 
468         if (mIsScalableGrid && inv.folderStyle != INVALID_RESOURCE_HANDLE) {
469             TypedArray folderStyle = context.obtainStyledAttributes(inv.folderStyle,
470                     R.styleable.FolderStyle);
471             // These are re-set in #updateFolderCellSize if the grid is not scalable
472             folderCellHeightPx = folderStyle.getDimensionPixelSize(
473                     R.styleable.FolderStyle_folderCellHeight, 0);
474             folderCellWidthPx = folderStyle.getDimensionPixelSize(
475                     R.styleable.FolderStyle_folderCellWidth, 0);
476 
477             folderContentPaddingTop = folderStyle.getDimensionPixelSize(
478                     R.styleable.FolderStyle_folderTopPadding, 0);
479 
480             int gutter = folderStyle.getDimensionPixelSize(
481                     R.styleable.FolderStyle_folderBorderSpace, 0);
482             folderCellLayoutBorderSpacePx = new Point(gutter, gutter);
483             folderFooterHeightPx = folderStyle.getDimensionPixelSize(
484                     R.styleable.FolderStyle_folderFooterHeight, 0);
485             folderStyle.recycle();
486         } else if (!mIsResponsiveGrid) {
487             folderCellLayoutBorderSpacePx = new Point(0, 0);
488             folderFooterHeightPx = res.getDimensionPixelSize(R.dimen.folder_footer_height_default);
489             folderContentPaddingTop = res.getDimensionPixelSize(R.dimen.folder_top_padding_default);
490         }
491 
492         allAppsBorderSpacePx = new Point(
493                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics),
494                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics));
495         setupAllAppsStyle(context);
496 
497         workspacePageIndicatorHeight = res.getDimensionPixelSize(
498                 R.dimen.workspace_page_indicator_height);
499         mWorkspacePageIndicatorOverlapWorkspace =
500                 res.getDimensionPixelSize(R.dimen.workspace_page_indicator_overlap_workspace);
501 
502         if (!mIsResponsiveGrid) {
503             TypedArray cellStyle;
504             if (inv.cellStyle != INVALID_RESOURCE_HANDLE) {
505                 cellStyle = context.obtainStyledAttributes(inv.cellStyle,
506                         R.styleable.CellStyle);
507             } else {
508                 cellStyle = context.obtainStyledAttributes(R.style.CellStyleDefault,
509                         R.styleable.CellStyle);
510             }
511             mIconDrawablePaddingOriginalPx = cellStyle.getDimensionPixelSize(
512                     R.styleable.CellStyle_iconDrawablePadding, 0);
513             cellStyle.recycle();
514         }
515 
516         dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
517         // Some foldable portrait modes are too wide in terms of aspect ratio so we need to tweak
518         // the dimensions for edit state.
519         final boolean shouldApplyWidePortraitDimens = isTablet
520                 && !isLandscape
521                 && aspectRatio < MAX_ASPECT_RATIO_FOR_ALTERNATE_EDIT_STATE;
522         dropTargetBarTopMarginPx = shouldApplyWidePortraitDimens
523                 ? 0
524                 : res.getDimensionPixelSize(R.dimen.drop_target_top_margin);
525         dropTargetBarBottomMarginPx = shouldApplyWidePortraitDimens
526                 ? res.getDimensionPixelSize(R.dimen.drop_target_bottom_margin_wide_portrait)
527                 : res.getDimensionPixelSize(R.dimen.drop_target_bottom_margin);
528         dropTargetDragPaddingPx = res.getDimensionPixelSize(R.dimen.drop_target_drag_padding);
529         dropTargetTextSizePx = res.getDimensionPixelSize(R.dimen.drop_target_text_size);
530         dropTargetHorizontalPaddingPx = res.getDimensionPixelSize(
531                 R.dimen.drop_target_button_drawable_horizontal_padding);
532         dropTargetVerticalPaddingPx = res.getDimensionPixelSize(
533                 R.dimen.drop_target_button_drawable_vertical_padding);
534         dropTargetGapPx = res.getDimensionPixelSize(R.dimen.drop_target_button_gap);
535         dropTargetButtonWorkspaceEdgeGapPx = res.getDimensionPixelSize(
536                 R.dimen.drop_target_button_workspace_edge_gap);
537 
538         workspaceSpringLoadedMinNextPageVisiblePx = res.getDimensionPixelSize(
539                 R.dimen.dynamic_grid_spring_loaded_min_next_space_visible);
540 
541         workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
542 
543         hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height);
544         hotseatQsbShadowHeight = res.getDimensionPixelSize(R.dimen.qsb_shadow_height);
545         hotseatQsbVisualHeight = hotseatQsbHeight - 2 * hotseatQsbShadowHeight;
546 
547         // Whether QSB might be inline in appropriate orientation (e.g. landscape).
548         boolean canQsbInline = (isTwoPanels ? inv.inlineQsb[INDEX_TWO_PANEL_PORTRAIT]
549                 || inv.inlineQsb[INDEX_TWO_PANEL_LANDSCAPE]
550                 : inv.inlineQsb[INDEX_DEFAULT] || inv.inlineQsb[INDEX_LANDSCAPE])
551                 && hotseatQsbHeight > 0;
552         isQsbInline = mIsScalableGrid && inv.inlineQsb[mTypeIndex] && canQsbInline;
553 
554         areNavButtonsInline = isTaskbarPresent && !isGestureMode;
555         numShownHotseatIcons =
556                 isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons;
557         mHotseatColumnSpan = inv.numColumns;
558 
559         numShownAllAppsColumns =
560                 isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns;
561 
562         int hotseatBarBottomSpace;
563         int minQsbMargin = res.getDimensionPixelSize(R.dimen.min_qsb_margin);
564 
565         if (mIsResponsiveGrid) {
566             float responsiveAspectRatio = (float) widthPx / heightPx;
567             HotseatSpecsProvider hotseatSpecsProvider =
568                     HotseatSpecsProvider.create(new ResourceHelper(context,
569                             isTwoPanels ? inv.hotseatSpecsTwoPanelId : inv.hotseatSpecsId));
570             mResponsiveHotseatSpec =
571                     isVerticalBarLayout() ? hotseatSpecsProvider.getCalculatedSpec(
572                             responsiveAspectRatio, DimensionType.WIDTH, widthPx)
573                             : hotseatSpecsProvider.getCalculatedSpec(responsiveAspectRatio,
574                                     DimensionType.HEIGHT, heightPx);
575             hotseatQsbSpace = mResponsiveHotseatSpec.getHotseatQsbSpace();
576             hotseatBarBottomSpace =
577                     isVerticalBarLayout() ? 0 : mResponsiveHotseatSpec.getEdgePadding();
578             mHotseatBarEdgePaddingPx =
579                     isVerticalBarLayout() ? mResponsiveHotseatSpec.getEdgePadding() : 0;
580             mHotseatBarWorkspaceSpacePx = 0;
581 
582             ResponsiveCellSpecsProvider workspaceCellSpecs = ResponsiveCellSpecsProvider.create(
583                     new ResourceHelper(context,
584                             isTwoPanels ? inv.workspaceCellSpecsTwoPanelId
585                                     : inv.workspaceCellSpecsId));
586             mResponsiveWorkspaceCellSpec = workspaceCellSpecs.getCalculatedSpec(
587                     responsiveAspectRatio, heightPx);
588         } else {
589             hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics);
590             hotseatBarBottomSpace = pxFromDp(inv.hotseatBarBottomSpace[mTypeIndex], mMetrics);
591             mHotseatBarEdgePaddingPx =
592                     isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
593             mHotseatBarWorkspaceSpacePx =
594                     res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
595         }
596 
597         if (!isVerticalBarLayout()) {
598             // Have a little space between the inset and the QSB
599             if (mInsets.bottom + minQsbMargin > hotseatBarBottomSpace) {
600                 int availableSpace = hotseatQsbSpace - (mInsets.bottom - hotseatBarBottomSpace);
601 
602                 // Only change the spaces if there is space
603                 if (availableSpace > 0) {
604                     // Make sure there is enough space between hotseat/QSB and QSB/navBar
605                     if (availableSpace < minQsbMargin * 2) {
606                         minQsbMargin = availableSpace / 2;
607                         hotseatQsbSpace = minQsbMargin;
608                     } else {
609                         hotseatQsbSpace -= minQsbMargin;
610                     }
611                 }
612                 hotseatBarBottomSpacePx = mInsets.bottom + minQsbMargin;
613 
614             } else {
615                 hotseatBarBottomSpacePx = hotseatBarBottomSpace;
616             }
617         }
618 
619         springLoadedHotseatBarTopMarginPx = shouldApplyWidePortraitDimens
620                 ? res.getDimensionPixelSize(R.dimen.spring_loaded_hotseat_top_margin_wide_portrait)
621                 : res.getDimensionPixelSize(R.dimen.spring_loaded_hotseat_top_margin);
622 
623         if (mIsResponsiveGrid) {
624             updateHotseatSizes(mResponsiveWorkspaceCellSpec.getIconSize());
625         } else {
626             updateHotseatSizes(pxFromDp(inv.iconSize[mTypeIndex], mMetrics));
627         }
628 
629         if (areNavButtonsInline && !isPhone) {
630             inlineNavButtonsEndSpacingPx =
631                     res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing);
632             /*
633              * 3 nav buttons +
634              * Spacing between nav buttons +
635              * Space at the end for contextual buttons
636              */
637             hotseatBarEndOffset = 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
638                     + 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween)
639                     + inlineNavButtonsEndSpacingPx;
640         } else {
641             inlineNavButtonsEndSpacingPx = 0;
642             hotseatBarEndOffset = 0;
643         }
644 
645         mBubbleBarSpaceThresholdPx =
646                 res.getDimensionPixelSize(R.dimen.bubblebar_hotseat_adjustment_threshold);
647 
648         // Needs to be calculated after hotseatBarSizePx is correct,
649         // for the available height to be correct
650         if (mIsResponsiveGrid) {
651             int availableResponsiveWidth =
652                     availableWidthPx - (isVerticalBarLayout() ? hotseatBarSizePx : 0);
653             int numWorkspaceColumns = getPanelCount() * inv.numColumns;
654             // don't use availableHeightPx because it subtracts mInsets.bottom
655             int availableResponsiveHeight = heightPx - mInsets.top
656                     - (isVerticalBarLayout() ? 0 : hotseatBarSizePx);
657             float responsiveAspectRatio = (float) widthPx / heightPx;
658 
659             ResponsiveSpecsProvider workspaceSpecs = ResponsiveSpecsProvider.create(
660                     new ResourceHelper(context,
661                             isTwoPanels ? inv.workspaceSpecsTwoPanelId : inv.workspaceSpecsId),
662                     ResponsiveSpecType.Workspace);
663             mResponsiveWorkspaceWidthSpec = workspaceSpecs.getCalculatedSpec(responsiveAspectRatio,
664                     DimensionType.WIDTH, numWorkspaceColumns, availableResponsiveWidth);
665             mResponsiveWorkspaceHeightSpec = workspaceSpecs.getCalculatedSpec(responsiveAspectRatio,
666                     DimensionType.HEIGHT, inv.numRows, availableResponsiveHeight);
667 
668             ResponsiveSpecsProvider allAppsSpecs = ResponsiveSpecsProvider.create(
669                     new ResourceHelper(context,
670                             isTwoPanels ? inv.allAppsSpecsTwoPanelId : inv.allAppsSpecsId),
671                     ResponsiveSpecType.AllApps);
672             mResponsiveAllAppsWidthSpec = allAppsSpecs.getCalculatedSpec(responsiveAspectRatio,
673                     DimensionType.WIDTH, numShownAllAppsColumns, availableWidthPx,
674                     mResponsiveWorkspaceWidthSpec);
675             mResponsiveAllAppsHeightSpec = allAppsSpecs.getCalculatedSpec(responsiveAspectRatio,
676                     DimensionType.HEIGHT, inv.numAllAppsRowsForCellHeightCalculation,
677                     heightPx - mInsets.top, mResponsiveWorkspaceHeightSpec);
678 
679             ResponsiveSpecsProvider folderSpecs = ResponsiveSpecsProvider.create(
680                     new ResourceHelper(context,
681                             isTwoPanels ? inv.folderSpecsTwoPanelId : inv.folderSpecsId),
682                     ResponsiveSpecType.Folder);
683             mResponsiveFolderWidthSpec = folderSpecs.getCalculatedSpec(responsiveAspectRatio,
684                     DimensionType.WIDTH, numFolderColumns,
685                     mResponsiveWorkspaceWidthSpec.getAvailableSpace(),
686                     mResponsiveWorkspaceWidthSpec);
687             mResponsiveFolderHeightSpec = folderSpecs.getCalculatedSpec(responsiveAspectRatio,
688                     DimensionType.HEIGHT, numFolderRows,
689                     mResponsiveWorkspaceHeightSpec.getAvailableSpace(),
690                     mResponsiveWorkspaceHeightSpec);
691 
692             ResponsiveCellSpecsProvider allAppsCellSpecs = ResponsiveCellSpecsProvider.create(
693                     new ResourceHelper(context,
694                             isTwoPanels ? inv.allAppsCellSpecsTwoPanelId
695                                     : inv.allAppsCellSpecsId));
696             mResponsiveAllAppsCellSpec = allAppsCellSpecs.getCalculatedSpec(
697                     responsiveAspectRatio,
698                     mResponsiveAllAppsHeightSpec.getAvailableSpace(),
699                     mResponsiveWorkspaceCellSpec);
700         }
701 
702         desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
703         desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx;
704 
705         overviewTaskMarginPx = res.getDimensionPixelSize(R.dimen.overview_task_margin);
706         overviewTaskIconSizePx = enableOverviewIconMenu() ? res.getDimensionPixelSize(
707                 R.dimen.task_thumbnail_icon_menu_drawable_touch_size) : res.getDimensionPixelSize(
708                 R.dimen.task_thumbnail_icon_size);
709         overviewTaskIconDrawableSizePx =
710                 res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size);
711         overviewTaskIconDrawableSizeGridPx =
712                 res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid);
713         overviewTaskThumbnailTopMarginPx =
714                 enableOverviewIconMenu() ? 0 : overviewTaskIconSizePx + overviewTaskMarginPx;
715         // Don't add margin with floating search bar to minimize risk of overlapping.
716         overviewActionsTopMarginPx = Flags.floatingSearchBar() ? 0
717                 : res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
718         overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
719         overviewActionsButtonSpacing = res.getDimensionPixelSize(
720                 R.dimen.overview_actions_button_spacing);
721         overviewActionsHeight = res.getDimensionPixelSize(R.dimen.overview_actions_height);
722         overviewRowSpacing = res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing);
723         overviewGridSideMargin = res.getDimensionPixelSize(R.dimen.overview_grid_side_margin);
724 
725         splitPlaceholderInset = res.getDimensionPixelSize(R.dimen.split_placeholder_inset);
726         // We need to use the full window bounds for split determination because on near-square
727         // devices, the available bounds (bounds minus insets) may actually be in landscape while
728         // actually portrait
729         int leftRightSplitPortraitResId = Resources.getSystem().getIdentifier(
730                 "config_leftRightSplitInPortrait", "bool", "android");
731         boolean allowLeftRightSplitInPortrait =
732                 com.android.wm.shell.Flags.enableLeftRightSplitInPortrait()
733                     && leftRightSplitPortraitResId > 0
734                     && res.getBoolean(leftRightSplitPortraitResId);
735         if (allowLeftRightSplitInPortrait && isTablet) {
736             isLeftRightSplit = !isLandscape;
737         } else {
738             isLeftRightSplit = isLandscape;
739         }
740 
741         // Calculate all of the remaining variables.
742         extraSpace = updateAvailableDimensions(context);
743 
744         calculateAndSetWorkspaceVerticalPadding(context, inv, extraSpace);
745 
746         int cellLayoutPadding =
747                 isTwoPanels ? cellLayoutBorderSpacePx.x / 2 : res.getDimensionPixelSize(
748                         R.dimen.cell_layout_padding);
749         cellLayoutPaddingPx = new Rect(cellLayoutPadding, cellLayoutPadding, cellLayoutPadding,
750                 cellLayoutPadding);
751         updateWorkspacePadding();
752 
753         // Folder scaling requires correct workspace paddings
754         updateAvailableFolderCellDimensions(res);
755 
756         mMinHotseatIconSpacePx = res.getDimensionPixelSize(R.dimen.min_hotseat_icon_space);
757         mMinHotseatQsbWidthPx = res.getDimensionPixelSize(R.dimen.min_hotseat_qsb_width);
758         mMaxHotseatIconSpacePx = areNavButtonsInline
759                 ? res.getDimensionPixelSize(R.dimen.max_hotseat_icon_space) : Integer.MAX_VALUE;
760         // Hotseat and QSB width depends on updated cellSize and workspace padding
761         recalculateHotseatWidthAndBorderSpace();
762 
763         if (mIsResponsiveGrid && isVerticalBarLayout()) {
764             hotseatBorderSpace = cellLayoutBorderSpacePx.y;
765         }
766 
767         if (isTablet) {
768             allAppsPadding.top = mInsets.top;
769             allAppsShiftRange = heightPx;
770         } else {
771             allAppsPadding.top = 0;
772             allAppsShiftRange =
773                     res.getDimensionPixelSize(R.dimen.all_apps_starting_vertical_translate);
774         }
775         allAppsOpenDuration = res.getInteger(R.integer.config_allAppsOpenDuration);
776         allAppsCloseDuration = res.getInteger(R.integer.config_allAppsCloseDuration);
777 
778         flingToDeleteThresholdVelocity = res.getDimensionPixelSize(
779                 R.dimen.drag_flingToDeleteMinVelocity);
780 
781         mViewScaleProvider = viewScaleProvider;
782 
783         dimensionOverrideProvider.accept(this);
784 
785         // This is done last, after iconSizePx is calculated above.
786         mDotRendererWorkSpace = createDotRenderer(context, iconSizePx, dotRendererCache);
787         mDotRendererAllApps = createDotRenderer(context, allAppsIconSizePx, dotRendererCache);
788     }
789 
createDotRenderer( @onNull Context context, int size, @NonNull SparseArray<DotRenderer> cache)790     private static DotRenderer createDotRenderer(
791             @NonNull Context context, int size, @NonNull SparseArray<DotRenderer> cache) {
792         DotRenderer renderer = cache.get(size);
793         if (renderer == null) {
794             renderer = new DotRenderer(size, getShapePath(context, DEFAULT_DOT_SIZE),
795                     DEFAULT_DOT_SIZE);
796             cache.put(size, renderer);
797         }
798         return renderer;
799     }
800 
801     /**
802      * Return maximum of all apps row count displayed on screen. Note that 1) Partially displayed
803      * row is counted as 1 row, and 2) we don't exclude the space of floating search bar. This
804      * method is used for calculating number of {@link BubbleTextView} we need to pre-inflate. Thus
805      * reasonable over estimation is fine.
806      */
getMaxAllAppsRowCount()807     public int getMaxAllAppsRowCount() {
808         return (int) (Math.ceil((availableHeightPx - allAppsPadding.top)
809                 / (float) allAppsCellHeightPx));
810     }
811 
812     /**
813      * QSB width is always calculated because when in 3 button nav the width doesn't follow the
814      * width of the hotseat.
815      */
calculateQsbWidth(int hotseatBorderSpace)816     private int calculateQsbWidth(int hotseatBorderSpace) {
817         int iconExtraSpacePx = iconSizePx - getIconVisibleSizePx(iconSizePx);
818         if (isQsbInline) {
819             int columns = getPanelCount() * inv.numColumns;
820             return getIconToIconWidthForColumns(columns)
821                     - iconSizePx * numShownHotseatIcons
822                     - hotseatBorderSpace * numShownHotseatIcons
823                     - iconExtraSpacePx;
824         } else {
825             return getIconToIconWidthForColumns(mHotseatColumnSpan) - iconExtraSpacePx;
826         }
827     }
828 
getIconToIconWidthForColumns(int columns)829     private int getIconToIconWidthForColumns(int columns) {
830         return columns * getCellSize().x
831                 + (columns - 1) * cellLayoutBorderSpacePx.x
832                 - getCellHorizontalSpace();
833     }
834 
getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res)835     private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) {
836         if (mIsResponsiveGrid) {
837             return mResponsiveWorkspaceWidthSpec.getStartPaddingPx();
838         }
839 
840         if (isVerticalBarLayout()) {
841             return 0;
842         }
843 
844         return mIsScalableGrid
845                 ? pxFromDp(idp.horizontalMargin[mTypeIndex], mMetrics)
846                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_left_right_margin);
847     }
848 
calculateAndSetWorkspaceVerticalPadding(Context context, InvariantDeviceProfile inv, int extraSpace)849     private void calculateAndSetWorkspaceVerticalPadding(Context context,
850             InvariantDeviceProfile inv,
851             int extraSpace) {
852         if (mIsResponsiveGrid) {
853             workspaceTopPadding = mResponsiveWorkspaceHeightSpec.getStartPaddingPx();
854             workspaceBottomPadding = mResponsiveWorkspaceHeightSpec.getEndPaddingPx();
855         } else if (mIsScalableGrid && inv.devicePaddingId != INVALID_RESOURCE_HANDLE) {
856             // Paddings were created assuming no scaling, so we first unscale the extra space.
857             int unscaledExtraSpace = (int) (extraSpace / cellScaleToFit);
858             DevicePaddings devicePaddings = new DevicePaddings(context, inv.devicePaddingId);
859             DevicePadding padding = devicePaddings.getDevicePadding(unscaledExtraSpace);
860             maxEmptySpace = padding.getMaxEmptySpacePx();
861 
862             int paddingWorkspaceTop = padding.getWorkspaceTopPadding(unscaledExtraSpace);
863             int paddingWorkspaceBottom = padding.getWorkspaceBottomPadding(unscaledExtraSpace);
864 
865             workspaceTopPadding = Math.round(paddingWorkspaceTop * cellScaleToFit);
866             workspaceBottomPadding = Math.round(paddingWorkspaceBottom * cellScaleToFit);
867         }
868     }
869 
870     /** Updates hotseatCellHeightPx and hotseatBarSizePx */
updateHotseatSizes(int hotseatIconSizePx)871     private void updateHotseatSizes(int hotseatIconSizePx) {
872         // Ensure there is enough space for folder icons, which have a slightly larger radius.
873         hotseatCellHeightPx = getIconSizeWithOverlap(hotseatIconSizePx);
874 
875         if (isVerticalBarLayout()) {
876             hotseatBarSizePx = hotseatIconSizePx + mHotseatBarEdgePaddingPx
877                     + mHotseatBarWorkspaceSpacePx;
878         } else if (isQsbInline) {
879             hotseatBarSizePx = Math.max(hotseatIconSizePx, hotseatQsbVisualHeight)
880                     + hotseatBarBottomSpacePx;
881         } else {
882             hotseatBarSizePx = hotseatIconSizePx
883                     + hotseatQsbSpace
884                     + hotseatQsbVisualHeight
885                     + hotseatBarBottomSpacePx;
886         }
887     }
888 
889     /**
890      * Calculates the width of the hotseat, changing spaces between the icons and removing icons if
891      * necessary.
892      */
recalculateHotseatWidthAndBorderSpace()893     public void recalculateHotseatWidthAndBorderSpace() {
894         if (!mIsScalableGrid) return;
895 
896         updateHotseatWidthAndBorderSpace(inv.numColumns);
897         int numWorkspaceColumns = getPanelCount() * inv.numColumns;
898         if (isTwoPanels) {
899             updateHotseatWidthAndBorderSpace(inv.numDatabaseHotseatIcons);
900             // If hotseat doesn't fit with current width, increase column span to fit by multiple
901             // of 2.
902             while (hotseatBorderSpace < mMinHotseatIconSpacePx
903                     && mHotseatColumnSpan < numWorkspaceColumns) {
904                 updateHotseatWidthAndBorderSpace(mHotseatColumnSpan + 2);
905             }
906         }
907         if (isQsbInline) {
908             // If QSB is inline, reduce column span until it fits.
909             int maxHotseatWidthAllowedPx = getIconToIconWidthForColumns(numWorkspaceColumns);
910             int minHotseatWidthRequiredPx =
911                     mMinHotseatQsbWidthPx + hotseatBorderSpace + mHotseatWidthPx;
912             while (minHotseatWidthRequiredPx > maxHotseatWidthAllowedPx
913                     && mHotseatColumnSpan > 1) {
914                 updateHotseatWidthAndBorderSpace(mHotseatColumnSpan - 1);
915                 minHotseatWidthRequiredPx =
916                         mMinHotseatQsbWidthPx + hotseatBorderSpace + mHotseatWidthPx;
917             }
918         }
919         hotseatQsbWidth = calculateQsbWidth(hotseatBorderSpace);
920 
921         // Spaces should be correct when the nav buttons are not inline
922         if (!areNavButtonsInline) {
923             return;
924         }
925 
926         // The side space with inline buttons should be what is defined in InvariantDeviceProfile
927         int sideSpacePx = inlineNavButtonsEndSpacingPx;
928         int maxHotseatWidthPx = availableWidthPx - sideSpacePx - hotseatBarEndOffset;
929         int maxHotseatIconsWidthPx = maxHotseatWidthPx - (isQsbInline ? hotseatQsbWidth : 0);
930         hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx,
931                 (isQsbInline ? 1 : 0) + /* border between nav buttons and first icon */ 1);
932 
933         if (hotseatBorderSpace >= mMinHotseatIconSpacePx) {
934             return;
935         }
936 
937         // Border space can't be less than the minimum
938         hotseatBorderSpace = mMinHotseatIconSpacePx;
939         int requiredWidth = getHotseatRequiredWidth();
940 
941         // If there is an inline qsb, change its size
942         if (isQsbInline) {
943             hotseatQsbWidth -= requiredWidth - maxHotseatWidthPx;
944             if (hotseatQsbWidth >= mMinHotseatQsbWidthPx) {
945                 return;
946             }
947 
948             // QSB can't be less than the minimum
949             hotseatQsbWidth = mMinHotseatQsbWidthPx;
950         }
951 
952         maxHotseatIconsWidthPx = maxHotseatWidthPx - (isQsbInline ? hotseatQsbWidth : 0);
953 
954         // If it still doesn't fit, start removing icons
955         do {
956             numShownHotseatIcons--;
957             hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx,
958                     (isQsbInline ? 1 : 0) + /* border between nav buttons and first icon */ 1);
959         } while (hotseatBorderSpace < mMinHotseatIconSpacePx && numShownHotseatIcons > 1);
960     }
961 
updateHotseatWidthAndBorderSpace(int columns)962     private void updateHotseatWidthAndBorderSpace(int columns) {
963         mHotseatColumnSpan = columns;
964         mHotseatWidthPx = getIconToIconWidthForColumns(mHotseatColumnSpan);
965         hotseatBorderSpace = calculateHotseatBorderSpace(mHotseatWidthPx, /* numExtraBorder= */ 0);
966     }
967 
getCellLayoutBorderSpace(InvariantDeviceProfile idp)968     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
969         return getCellLayoutBorderSpace(idp, 1f);
970     }
971 
getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale)972     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale) {
973         int horizontalSpacePx = 0;
974         int verticalSpacePx = 0;
975 
976         if (mIsResponsiveGrid) {
977             horizontalSpacePx = mResponsiveWorkspaceWidthSpec.getGutterPx();
978             verticalSpacePx = mResponsiveWorkspaceHeightSpec.getGutterPx();
979         } else if (mIsScalableGrid) {
980             horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics, scale);
981             verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics, scale);
982         }
983 
984         return new Point(horizontalSpacePx, verticalSpacePx);
985     }
986 
getDisplayInfo()987     public Info getDisplayInfo() {
988         return mInfo;
989     }
990 
991     @VisibleForTesting
getHotseatColumnSpan()992     public int getHotseatColumnSpan() {
993         return mHotseatColumnSpan;
994     }
995 
996     @VisibleForTesting
getHotseatWidthPx()997     public int getHotseatWidthPx() {
998         return mHotseatWidthPx;
999     }
1000 
toBuilder(Context context)1001     public Builder toBuilder(Context context) {
1002         WindowBounds bounds = new WindowBounds(
1003                 widthPx, heightPx, availableWidthPx, availableHeightPx, rotationHint);
1004         bounds.bounds.offsetTo(windowX, windowY);
1005         bounds.insets.set(mInsets);
1006 
1007         SparseArray<DotRenderer> dotRendererCache = new SparseArray<>();
1008         dotRendererCache.put(iconSizePx, mDotRendererWorkSpace);
1009         dotRendererCache.put(allAppsIconSizePx, mDotRendererAllApps);
1010 
1011         return new Builder(context, inv, mInfo)
1012                 .setWindowBounds(bounds)
1013                 .setIsMultiDisplay(isMultiDisplay)
1014                 .setMultiWindowMode(isMultiWindowMode)
1015                 .setDotRendererCache(dotRendererCache)
1016                 .setGestureMode(isGestureMode);
1017     }
1018 
copy(Context context)1019     public DeviceProfile copy(Context context) {
1020         return toBuilder(context).build();
1021     }
1022 
1023     /**
1024      * TODO: Move this to the builder as part of setMultiWindowMode
1025      */
getMultiWindowProfile(Context context, WindowBounds windowBounds)1026     public DeviceProfile getMultiWindowProfile(Context context, WindowBounds windowBounds) {
1027         DeviceProfile profile = toBuilder(context)
1028                 .setWindowBounds(windowBounds)
1029                 .setMultiWindowMode(true)
1030                 .build();
1031 
1032         // We use these scales to measure and layout the widgets using their full invariant profile
1033         // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
1034         float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
1035         float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
1036         if (appWidgetScaleX != 1 || appWidgetScaleY != 1) {
1037             final PointF p = new PointF(appWidgetScaleX, appWidgetScaleY);
1038             profile = profile.toBuilder(context)
1039                     .setViewScaleProvider(i -> p)
1040                     .build();
1041         }
1042 
1043         profile.hideWorkspaceLabelsIfNotEnoughSpace();
1044 
1045         return profile;
1046     }
1047 
1048     /**
1049      * Checks if there is enough space for labels on the workspace.
1050      * If there is not, labels on the Workspace are hidden.
1051      * It is important to call this method after the All Apps variables have been set.
1052      */
hideWorkspaceLabelsIfNotEnoughSpace()1053     private void hideWorkspaceLabelsIfNotEnoughSpace() {
1054         float iconTextHeight = Utilities.calculateTextHeight(iconTextSizePx);
1055         float workspaceCellPaddingY = getCellSize().y - iconSizePx - iconDrawablePaddingPx
1056                 - iconTextHeight;
1057 
1058         // We want enough space so that the text is closer to its corresponding icon.
1059         if (workspaceCellPaddingY < iconTextHeight) {
1060             iconTextSizePx = 0;
1061             iconDrawablePaddingPx = 0;
1062             cellHeightPx = getIconSizeWithOverlap(iconSizePx);
1063             autoResizeAllAppsCells();
1064         }
1065     }
1066 
1067     /**
1068      * Returns the amount of extra (or unused) vertical space.
1069      */
updateAvailableDimensions(Context context)1070     private int updateAvailableDimensions(Context context) {
1071         iconCenterVertically = (mIsScalableGrid || mIsResponsiveGrid) && isVerticalBarLayout();
1072 
1073         if (mIsResponsiveGrid) {
1074             iconSizePx = mResponsiveWorkspaceCellSpec.getIconSize();
1075             iconTextSizePx = mResponsiveWorkspaceCellSpec.getIconTextSize();
1076             mIconDrawablePaddingOriginalPx = mResponsiveWorkspaceCellSpec.getIconDrawablePadding();
1077             updateIconSize(1f, context);
1078             updateWorkspacePadding();
1079             return 0;
1080         }
1081 
1082         float invIconSizeDp = inv.iconSize[mTypeIndex];
1083         float invIconTextSizeSp = inv.iconTextSize[mTypeIndex];
1084         iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics));
1085         iconTextSizePx = pxFromSp(invIconTextSizeSp, mMetrics);
1086 
1087         updateIconSize(1f, context);
1088         updateWorkspacePadding();
1089 
1090         // Check to see if the icons fit within the available height.
1091         float usedHeight = getCellLayoutHeightSpecification();
1092         final int maxHeight = getCellLayoutHeight();
1093         float extraHeight = Math.max(0, maxHeight - usedHeight);
1094         float scaleY = maxHeight / usedHeight;
1095         boolean shouldScale = scaleY < 1f;
1096 
1097         float scaleX = 1f;
1098         if (mIsScalableGrid) {
1099             // We scale to fit the cellWidth and cellHeight in the available space.
1100             // The benefit of scalable grids is that we can get consistent aspect ratios between
1101             // devices.
1102             float usedWidth =
1103                     getCellLayoutWidthSpecification() + (desiredWorkspaceHorizontalMarginPx * 2);
1104             // We do not subtract padding here, as we also scale the workspace padding if needed.
1105             scaleX = availableWidthPx / usedWidth;
1106             shouldScale = true;
1107         }
1108 
1109         if (shouldScale) {
1110             float scale = Math.min(scaleX, scaleY);
1111             updateIconSize(scale, context);
1112             extraHeight = Math.max(0, maxHeight - getCellLayoutHeightSpecification());
1113         }
1114 
1115         return Math.round(extraHeight);
1116     }
1117 
1118     private int getCellLayoutHeightSpecification() {
1119         return (cellHeightPx * inv.numRows) + (cellLayoutBorderSpacePx.y * (inv.numRows - 1))
1120                 + cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom;
1121     }
1122 
1123     private int getCellLayoutWidthSpecification() {
1124         int numColumns = getPanelCount() * inv.numColumns;
1125         return (cellWidthPx * numColumns) + (cellLayoutBorderSpacePx.x * (numColumns - 1))
1126                 + cellLayoutPaddingPx.left + cellLayoutPaddingPx.right;
1127     }
1128 
1129     private int getNormalizedIconDrawablePadding(int iconSizePx, int iconDrawablePadding) {
1130         return Math.max(0, iconDrawablePadding
1131                 - ((iconSizePx - getIconVisibleSizePx(iconSizePx)) / 2));
1132     }
1133 
1134     private int getNormalizedIconDrawablePadding() {
1135         return getNormalizedIconDrawablePadding(iconSizePx, mIconDrawablePaddingOriginalPx);
1136     }
1137 
1138     private int getNormalizedFolderChildDrawablePaddingPx(int textHeight) {
1139         // TODO(b/235886078): workaround needed because of this bug
1140         // Icons are 10% larger on XML than their visual size,
1141         // so remove that extra space to get labels closer to the correct padding
1142         int drawablePadding = (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3;
1143 
1144         int iconSizeDiff = folderChildIconSizePx - getIconVisibleSizePx(folderChildIconSizePx);
1145         return Math.max(0, drawablePadding - iconSizeDiff / 2);
1146     }
1147 
1148     private int getIconSizeWithOverlap(int iconSize) {
1149         return (int) Math.ceil(iconSize * ICON_OVERLAP_FACTOR);
1150     }
1151 
1152     /**
1153      * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx,
1154      * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
1155      * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
1156      */
1157     public void updateIconSize(float scale, Context context) {
1158         // Icon scale should never exceed 1, otherwise pixellation may occur.
1159         iconScale = Math.min(1f, scale);
1160         cellScaleToFit = scale;
1161 
1162         // Workspace
1163         final boolean isVerticalLayout = isVerticalBarLayout();
1164         cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv, scale);
1165 
1166         if (mIsResponsiveGrid) {
1167             cellWidthPx = mResponsiveWorkspaceWidthSpec.getCellSizePx();
1168             cellHeightPx = mResponsiveWorkspaceHeightSpec.getCellSizePx();
1169 
1170             if (cellWidthPx < iconSizePx) {
1171                 // get a smaller icon size
1172                 iconSizePx = mIconSizeSteps.getIconSmallerThan(cellWidthPx);
1173             }
1174 
1175             if (isVerticalLayout) {
1176                 iconDrawablePaddingPx = 0;
1177                 iconTextSizePx = 0;
1178             } else {
1179                 iconDrawablePaddingPx = getNormalizedIconDrawablePadding();
1180             }
1181 
1182             CellContentDimensions cellContentDimensions = new CellContentDimensions(iconSizePx,
1183                     iconDrawablePaddingPx,
1184                     iconTextSizePx);
1185             int cellContentHeight = cellContentDimensions.resizeToFitCellHeight(cellHeightPx,
1186                     mIconSizeSteps);
1187             iconSizePx = cellContentDimensions.getIconSizePx();
1188             iconDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx();
1189             iconTextSizePx = cellContentDimensions.getIconTextSizePx();
1190 
1191             if (isVerticalLayout) {
1192                 cellYPaddingPx = Math.max(0, getCellSize().y - getIconSizeWithOverlap(iconSizePx))
1193                         / 2;
1194             } else {
1195                 cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
1196             }
1197         } else if (mIsScalableGrid) {
1198             iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale);
1199             cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale);
1200             cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale);
1201 
1202             if (cellWidthPx < iconSizePx) {
1203                 // If cellWidth no longer fit iconSize, reduce borderSpace to make cellWidth bigger.
1204                 int numColumns = getPanelCount() * inv.numColumns;
1205                 int numBorders = numColumns - 1;
1206                 int extraWidthRequired = (iconSizePx - cellWidthPx) * numColumns;
1207                 if (cellLayoutBorderSpacePx.x * numBorders >= extraWidthRequired) {
1208                     cellWidthPx = iconSizePx;
1209                     cellLayoutBorderSpacePx.x -= extraWidthRequired / numBorders;
1210                 } else {
1211                     // If it still doesn't fit, set borderSpace to 0 and distribute the space for
1212                     // cellWidth, and reduce iconSize.
1213                     cellWidthPx = (cellWidthPx * numColumns
1214                             + cellLayoutBorderSpacePx.x * numBorders) / numColumns;
1215                     iconSizePx = Math.min(iconSizePx, cellWidthPx);
1216                     cellLayoutBorderSpacePx.x = 0;
1217                 }
1218             }
1219 
1220             int cellTextAndPaddingHeight =
1221                     iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
1222             int cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
1223             if (cellHeightPx < cellContentHeight) {
1224                 // If cellHeight no longer fit iconSize, reduce borderSpace to make cellHeight
1225                 // bigger.
1226                 int numBorders = inv.numRows - 1;
1227                 int extraHeightRequired = (cellContentHeight - cellHeightPx) * inv.numRows;
1228                 if (cellLayoutBorderSpacePx.y * numBorders >= extraHeightRequired) {
1229                     cellHeightPx = cellContentHeight;
1230                     cellLayoutBorderSpacePx.y -= extraHeightRequired / numBorders;
1231                 } else {
1232                     // If it still doesn't fit, set borderSpace to 0 to recover space.
1233                     cellHeightPx = (cellHeightPx * inv.numRows
1234                             + cellLayoutBorderSpacePx.y * numBorders) / inv.numRows;
1235                     cellLayoutBorderSpacePx.y = 0;
1236                     // Reduce iconDrawablePaddingPx to make cellContentHeight smaller.
1237                     int cellContentWithoutPadding = cellContentHeight - iconDrawablePaddingPx;
1238                     if (cellContentWithoutPadding <= cellHeightPx) {
1239                         iconDrawablePaddingPx = cellContentHeight - cellHeightPx;
1240                     } else {
1241                         // If it still doesn't fit, set iconDrawablePaddingPx to 0 to recover space,
1242                         // then proportional reduce iconSizePx and iconTextSizePx to fit.
1243                         iconDrawablePaddingPx = 0;
1244                         float ratio = cellHeightPx / (float) cellContentWithoutPadding;
1245                         iconSizePx = (int) (iconSizePx * ratio);
1246                         iconTextSizePx = (int) (iconTextSizePx * ratio);
1247                     }
1248                     cellTextAndPaddingHeight =
1249                             iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
1250                 }
1251                 cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
1252             }
1253             cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
1254             desiredWorkspaceHorizontalMarginPx =
1255                     (int) (desiredWorkspaceHorizontalMarginOriginalPx * scale);
1256         } else {
1257             iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale);
1258             cellWidthPx = iconSizePx + iconDrawablePaddingPx;
1259             cellHeightPx = getIconSizeWithOverlap(iconSizePx)
1260                     + iconDrawablePaddingPx
1261                     + Utilities.calculateTextHeight(iconTextSizePx);
1262             int cellPaddingY = (getCellSize().y - cellHeightPx) / 2;
1263             if (iconDrawablePaddingPx > cellPaddingY && !isVerticalLayout
1264                     && !isMultiWindowMode) {
1265                 // Ensures that the label is closer to its corresponding icon. This is not an issue
1266                 // with vertical bar layout or multi-window mode since the issue is handled
1267                 // separately with their calls to {@link #adjustToHideWorkspaceLabels}.
1268                 cellHeightPx -= (iconDrawablePaddingPx - cellPaddingY);
1269                 iconDrawablePaddingPx = cellPaddingY;
1270             }
1271         }
1272 
1273         // All apps
1274         if (mIsResponsiveGrid) {
1275             updateAllAppsWithResponsiveMeasures();
1276         } else {
1277             updateAllAppsIconSize(scale, context.getResources());
1278         }
1279         updateAllAppsContainerWidth();
1280         if (isVerticalLayout && !mIsResponsiveGrid) {
1281             hideWorkspaceLabelsIfNotEnoughSpace();
1282         }
1283         if ((Flags.enableTwolineToggle()
1284                 && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(context))) {
1285             // Add extra textHeight to the existing allAppsCellHeight.
1286             allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx);
1287         }
1288 
1289         updateHotseatSizes(iconSizePx);
1290 
1291         // Folder icon
1292         folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
1293         folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
1294 
1295         // Update widget padding:
1296         float minSpacing = pxFromDp(MIN_WIDGET_PADDING_DP, mMetrics);
1297         if (cellLayoutBorderSpacePx.x < minSpacing
1298                 || cellLayoutBorderSpacePx.y < minSpacing) {
1299             widgetPadding.left = widgetPadding.right =
1300                     Math.round(Math.max(0, minSpacing - cellLayoutBorderSpacePx.x));
1301             widgetPadding.top = widgetPadding.bottom =
1302                     Math.round(Math.max(0, minSpacing - cellLayoutBorderSpacePx.y));
1303         } else {
1304             widgetPadding.setEmpty();
1305         }
1306     }
1307 
1308     /**
1309      * This method calculates the space between the icons to achieve a certain width.
1310      */
1311     private int calculateHotseatBorderSpace(float hotseatWidthPx, int numExtraBorder) {
1312         int numBorders = (numShownHotseatIcons - 1 + numExtraBorder);
1313         if (numBorders <= 0) return 0;
1314 
1315         float hotseatIconsTotalPx = iconSizePx * numShownHotseatIcons;
1316         int hotseatBorderSpacePx = (int) (hotseatWidthPx - hotseatIconsTotalPx) / numBorders;
1317         return Math.min(hotseatBorderSpacePx, mMaxHotseatIconSpacePx);
1318     }
1319 
1320     /**
1321      * Updates the iconSize for allApps* variants.
1322      */
1323     private void updateAllAppsIconSize(float scale, Resources res) {
1324         allAppsBorderSpacePx = new Point(
1325                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics, scale),
1326                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics, scale));
1327         // AllApps cells don't have real space between cells,
1328         // so we add the border space to the cell height
1329         allAppsCellHeightPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].y, mMetrics)
1330                 + allAppsBorderSpacePx.y;
1331         // but width is just the cell,
1332         // the border is added in #updateAllAppsContainerWidth
1333         if (mIsScalableGrid) {
1334             allAppsIconSizePx = pxFromDp(inv.allAppsIconSize[mTypeIndex], mMetrics);
1335             allAppsIconTextSizePx = pxFromSp(inv.allAppsIconTextSize[mTypeIndex], mMetrics);
1336             allAppsIconDrawablePaddingPx = getNormalizedIconDrawablePadding();
1337             allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics, scale);
1338 
1339             if (allAppsCellWidthPx < allAppsIconSizePx) {
1340                 // If allAppsCellWidth no longer fit allAppsIconSize, reduce allAppsBorderSpace to
1341                 // make allAppsCellWidth bigger.
1342                 int numBorders = inv.numAllAppsColumns - 1;
1343                 int extraWidthRequired =
1344                         (allAppsIconSizePx - allAppsCellWidthPx) * inv.numAllAppsColumns;
1345                 if (allAppsBorderSpacePx.x * numBorders >= extraWidthRequired) {
1346                     allAppsCellWidthPx = allAppsIconSizePx;
1347                     allAppsBorderSpacePx.x -= extraWidthRequired / numBorders;
1348                 } else {
1349                     // If it still doesn't fit, set allAppsBorderSpace to 0 and distribute the space
1350                     // for allAppsCellWidth, and reduce allAppsIconSize.
1351                     allAppsCellWidthPx = (allAppsCellWidthPx * inv.numAllAppsColumns
1352                             + allAppsBorderSpacePx.x * numBorders) / inv.numAllAppsColumns;
1353                     allAppsIconSizePx = Math.min(allAppsIconSizePx, allAppsCellWidthPx);
1354                     allAppsBorderSpacePx.x = 0;
1355                 }
1356             }
1357 
1358             int cellContentHeight = allAppsIconSizePx
1359                     + Utilities.calculateTextHeight(allAppsIconTextSizePx) + allAppsBorderSpacePx.y;
1360             if (allAppsCellHeightPx < cellContentHeight) {
1361                 // Increase allAppsCellHeight to fit its content.
1362                 allAppsCellHeightPx = cellContentHeight;
1363             }
1364         } else {
1365             float invIconSizeDp = inv.allAppsIconSize[mTypeIndex];
1366             float invIconTextSizeSp = inv.allAppsIconTextSize[mTypeIndex];
1367             allAppsIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale));
1368             allAppsIconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * scale);
1369             allAppsIconDrawablePaddingPx =
1370                     res.getDimensionPixelSize(R.dimen.all_apps_icon_drawable_padding);
1371             allAppsCellWidthPx = allAppsIconSizePx + (2 * allAppsIconDrawablePaddingPx);
1372         }
1373     }
1374 
1375     private void updateAllAppsWithResponsiveMeasures() {
1376         allAppsIconSizePx = mResponsiveAllAppsCellSpec.getIconSize();
1377         allAppsIconTextSizePx = mResponsiveAllAppsCellSpec.getIconTextSize();
1378         allAppsIconDrawablePaddingPx = getNormalizedIconDrawablePadding(allAppsIconSizePx,
1379                 mResponsiveAllAppsCellSpec.getIconDrawablePadding());
1380         allAppsBorderSpacePx = new Point(
1381                 mResponsiveAllAppsWidthSpec.getGutterPx(),
1382                 mResponsiveAllAppsHeightSpec.getGutterPx()
1383         );
1384         allAppsCellHeightPx = mResponsiveAllAppsHeightSpec.getCellSizePx();
1385         allAppsCellWidthPx = mResponsiveAllAppsWidthSpec.getCellSizePx();
1386 
1387         // This workaround is needed to align AllApps icons with Workspace icons
1388         // since AllApps doesn't have borders between cells
1389         int halfBorder = allAppsBorderSpacePx.x / 2;
1390         allAppsPadding.left = mResponsiveAllAppsWidthSpec.getStartPaddingPx() - halfBorder;
1391         allAppsPadding.right = mResponsiveAllAppsWidthSpec.getEndPaddingPx() - halfBorder;
1392 
1393 
1394         // Reduce the size of the app icon if it doesn't fit
1395         if (allAppsCellWidthPx < allAppsIconSizePx) {
1396             // get a smaller icon size
1397             allAppsIconSizePx = mIconSizeSteps.getIconSmallerThan(allAppsCellWidthPx);
1398         }
1399 
1400         CellContentDimensions cellContentDimensions = new CellContentDimensions(
1401                 allAppsIconSizePx, allAppsIconDrawablePaddingPx, (int) allAppsIconTextSizePx);
1402 
1403         if (allAppsCellHeightPx < cellContentDimensions.getCellContentHeight()) {
1404             if (isVerticalBarLayout()) {
1405                 if (allAppsCellHeightPx < allAppsIconSizePx) {
1406                     cellContentDimensions.setIconSizePx(
1407                             mIconSizeSteps.getIconSmallerThan(allAppsCellHeightPx));
1408                 }
1409             } else {
1410                 cellContentDimensions.resizeToFitCellHeight(allAppsCellHeightPx,
1411                         mIconSizeSteps);
1412             }
1413             allAppsIconSizePx = cellContentDimensions.getIconSizePx();
1414             allAppsIconDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx();
1415             allAppsIconTextSizePx = cellContentDimensions.getIconTextSizePx();
1416         }
1417 
1418         allAppsCellHeightPx += mResponsiveAllAppsHeightSpec.getGutterPx();
1419 
1420         if (isVerticalBarLayout()) {
1421             autoResizeAllAppsCells();
1422         }
1423     }
1424 
1425     /**
1426      * Re-computes the all-apps cell size to be independent of workspace
1427      */
1428     public void autoResizeAllAppsCells() {
1429         int textHeight = Utilities.calculateTextHeight(allAppsIconTextSizePx);
1430         int topBottomPadding = textHeight;
1431         allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
1432                 + textHeight + (topBottomPadding * 2);
1433     }
1434 
1435     private void updateAllAppsContainerWidth() {
1436         int cellLayoutHorizontalPadding =
1437                 (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right) / 2;
1438         if (isTablet) {
1439             int usedWidth = (allAppsCellWidthPx * numShownAllAppsColumns)
1440                     + (allAppsBorderSpacePx.x * (numShownAllAppsColumns - 1))
1441                     + allAppsPadding.left + allAppsPadding.right;
1442             allAppsLeftRightMargin = Math.max(1, (availableWidthPx - usedWidth) / 2);
1443         } else if (!mIsResponsiveGrid) {
1444             allAppsPadding.left = allAppsPadding.right =
1445                     Math.max(0, desiredWorkspaceHorizontalMarginPx + cellLayoutHorizontalPadding
1446                             - (allAppsBorderSpacePx.x / 2));
1447         }
1448     }
1449 
1450     private void setupAllAppsStyle(Context context) {
1451         TypedArray allAppsStyle = context.obtainStyledAttributes(
1452                 inv.allAppsStyle != INVALID_RESOURCE_HANDLE ? inv.allAppsStyle
1453                         : R.style.AllAppsStyleDefault, R.styleable.AllAppsStyle);
1454 
1455         allAppsPadding.left = allAppsPadding.right = allAppsStyle.getDimensionPixelSize(
1456                 R.styleable.AllAppsStyle_horizontalPadding, 0);
1457         allAppsStyle.recycle();
1458     }
1459 
1460     private void updateAvailableFolderCellDimensions(Resources res) {
1461         updateFolderCellSize(1f, res);
1462 
1463         // Responsive grid doesn't need to scale the folder
1464         if (mIsResponsiveGrid) return;
1465 
1466         // For usability we can't have the folder use the whole width of the screen
1467         Point totalWorkspacePadding = getTotalWorkspacePadding();
1468 
1469         // Check if the folder fit within the available height.
1470         float contentUsedHeight = folderCellHeightPx * numFolderRows
1471                 + ((numFolderRows - 1) * folderCellLayoutBorderSpacePx.y)
1472                 + folderFooterHeightPx
1473                 + folderContentPaddingTop;
1474         int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y;
1475         float scaleY = contentMaxHeight / contentUsedHeight;
1476 
1477         // Check if the folder fit within the available width.
1478         float contentUsedWidth = folderCellWidthPx * numFolderColumns
1479                 + ((numFolderColumns - 1) * folderCellLayoutBorderSpacePx.x)
1480                 + folderContentPaddingLeftRight * 2;
1481         int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x;
1482         float scaleX = contentMaxWidth / contentUsedWidth;
1483 
1484         float scale = Math.min(scaleX, scaleY);
1485         if (scale < 1f) {
1486             updateFolderCellSize(scale, res);
1487         }
1488     }
1489 
1490     private void updateFolderCellSize(float scale, Resources res) {
1491         int minLabelTextSize = pxFromSp(MIN_FOLDER_TEXT_SIZE_SP, mMetrics, scale);
1492         if (mIsResponsiveGrid) {
1493             folderChildIconSizePx = mResponsiveWorkspaceCellSpec.getIconSize();
1494             folderChildTextSizePx = mResponsiveWorkspaceCellSpec.getIconTextSize();
1495             folderLabelTextSizePx = Math.max(minLabelTextSize,
1496                     (int) (folderChildTextSizePx * folderLabelTextScale));
1497             int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
1498 
1499             folderCellWidthPx = mResponsiveFolderWidthSpec.getCellSizePx();
1500             folderCellHeightPx = mResponsiveFolderHeightSpec.getCellSizePx();
1501             folderContentPaddingTop = mResponsiveFolderHeightSpec.getStartPaddingPx();
1502             folderFooterHeightPx = mResponsiveFolderHeightSpec.getEndPaddingPx();
1503 
1504             folderCellLayoutBorderSpacePx = new Point(mResponsiveFolderWidthSpec.getGutterPx(),
1505                     mResponsiveFolderHeightSpec.getGutterPx());
1506 
1507             folderContentPaddingLeftRight = mResponsiveFolderWidthSpec.getStartPaddingPx();
1508 
1509             // Reduce icon width if it's wider than the expected folder cell width
1510             if (folderCellWidthPx < folderChildIconSizePx) {
1511                 folderChildIconSizePx = mIconSizeSteps.getIconSmallerThan(folderCellWidthPx);
1512             }
1513 
1514             // Recalculating padding and cell height
1515             folderChildDrawablePaddingPx = mResponsiveWorkspaceCellSpec.getIconDrawablePadding();
1516 
1517             CellContentDimensions cellContentDimensions = new CellContentDimensions(
1518                     folderChildIconSizePx,
1519                     folderChildDrawablePaddingPx,
1520                     folderChildTextSizePx);
1521             cellContentDimensions.resizeToFitCellHeight(folderCellHeightPx, mIconSizeSteps);
1522             folderChildIconSizePx = cellContentDimensions.getIconSizePx();
1523             folderChildDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx();
1524             folderChildTextSizePx = cellContentDimensions.getIconTextSizePx();
1525             folderLabelTextSizePx = Math.max(minLabelTextSize,
1526                     (int) (folderChildTextSizePx * folderLabelTextScale));
1527             return;
1528         }
1529 
1530         float invIconSizeDp = inv.iconSize[mTypeIndex];
1531         float invIconTextSizeDp = inv.iconTextSize[mTypeIndex];
1532         folderChildIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale));
1533         folderChildTextSizePx = pxFromSp(invIconTextSizeDp, mMetrics, scale);
1534         folderLabelTextSizePx = Math.max(minLabelTextSize,
1535                 (int) (folderChildTextSizePx * folderLabelTextScale));
1536         int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
1537 
1538         if (mIsScalableGrid) {
1539             if (inv.folderStyle == INVALID_RESOURCE_HANDLE) {
1540                 folderCellWidthPx = roundPxValueFromFloat(getCellSize().x * scale);
1541                 folderCellHeightPx = roundPxValueFromFloat(getCellSize().y * scale);
1542             } else {
1543                 folderCellWidthPx = roundPxValueFromFloat(folderCellWidthPx * scale);
1544                 folderCellHeightPx = roundPxValueFromFloat(folderCellHeightPx * scale);
1545             }
1546             // Recalculating padding and cell height
1547             folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
1548 
1549             CellContentDimensions cellContentDimensions = new CellContentDimensions(
1550                     folderChildIconSizePx,
1551                     folderChildDrawablePaddingPx,
1552                     folderChildTextSizePx);
1553             cellContentDimensions.resizeToFitCellHeight(folderCellHeightPx, mIconSizeSteps);
1554             folderChildIconSizePx = cellContentDimensions.getIconSizePx();
1555             folderChildDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx();
1556             folderChildTextSizePx = cellContentDimensions.getIconTextSizePx();
1557 
1558             folderContentPaddingTop = roundPxValueFromFloat(folderContentPaddingTop * scale);
1559             folderCellLayoutBorderSpacePx = new Point(
1560                     roundPxValueFromFloat(folderCellLayoutBorderSpacePx.x * scale),
1561                     roundPxValueFromFloat(folderCellLayoutBorderSpacePx.y * scale)
1562             );
1563             folderFooterHeightPx = roundPxValueFromFloat(folderFooterHeightPx * scale);
1564             folderContentPaddingLeftRight = folderCellLayoutBorderSpacePx.x;
1565         } else {
1566             int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding)
1567                     * scale);
1568             int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding)
1569                     * scale);
1570 
1571             folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
1572             folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
1573             folderContentPaddingTop = roundPxValueFromFloat(folderContentPaddingTop * scale);
1574             folderContentPaddingLeftRight =
1575                     res.getDimensionPixelSize(R.dimen.folder_content_padding_left_right);
1576             folderFooterHeightPx =
1577                     roundPxValueFromFloat(
1578                             res.getDimensionPixelSize(R.dimen.folder_footer_height_default)
1579                                     * scale);
1580 
1581             folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
1582         }
1583     }
1584 
1585     public void updateInsets(Rect insets) {
1586         mInsets.set(insets);
1587     }
1588 
1589     /**
1590      * The current device insets. This is generally same as the insets being dispatched to
1591      * {@link Insettable} elements, but can differ if the element is using a different profile.
1592      */
1593     public Rect getInsets() {
1594         return mInsets;
1595     }
1596 
1597     public Point getCellSize() {
1598         return getCellSize(null);
1599     }
1600 
1601     public Point getCellSize(Point result) {
1602         if (result == null) {
1603             result = new Point();
1604         }
1605 
1606         int shortcutAndWidgetContainerWidth =
1607                 getCellLayoutWidth() - (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right);
1608         result.x = calculateCellWidth(shortcutAndWidgetContainerWidth, cellLayoutBorderSpacePx.x,
1609                 inv.numColumns);
1610         int shortcutAndWidgetContainerHeight =
1611                 getCellLayoutHeight() - (cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom);
1612         result.y = calculateCellHeight(shortcutAndWidgetContainerHeight, cellLayoutBorderSpacePx.y,
1613                 inv.numRows);
1614         return result;
1615     }
1616 
1617     /**
1618      * Returns the left and right space on the cell, which is the cell width - icon size
1619      */
1620     public int getCellHorizontalSpace() {
1621         return getCellSize().x - iconSizePx;
1622     }
1623 
1624     /**
1625      * Gets the number of panels within the workspace.
1626      */
1627     public int getPanelCount() {
1628         return isTwoPanels ? 2 : 1;
1629     }
1630 
1631     /**
1632      * Gets the space in px from the bottom of last item in the vertical-bar hotseat to the
1633      * bottom of the screen.
1634      */
1635     private int getVerticalHotseatLastItemBottomOffset(Context context) {
1636         Rect hotseatBarPadding = getHotseatLayoutPadding(context);
1637         int cellHeight = calculateCellHeight(
1638                 heightPx - hotseatBarPadding.top - hotseatBarPadding.bottom, hotseatBorderSpace,
1639                 numShownHotseatIcons);
1640         int extraIconEndSpacing = (cellHeight - iconSizePx) / 2;
1641         return extraIconEndSpacing + hotseatBarPadding.bottom;
1642     }
1643 
1644     /**
1645      * Gets the scaled top of the workspace in px for the spring-loaded edit state.
1646      */
1647     public float getCellLayoutSpringLoadShrunkTop() {
1648         return mInsets.top + dropTargetBarTopMarginPx + dropTargetBarSizePx
1649                 + dropTargetBarBottomMarginPx;
1650     }
1651 
1652     /**
1653      * Gets the scaled bottom of the workspace in px for the spring-loaded edit state.
1654      */
1655     public float getCellLayoutSpringLoadShrunkBottom(Context context) {
1656         int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx;
1657         return heightPx - (isVerticalBarLayout()
1658                 ? getVerticalHotseatLastItemBottomOffset(context) : topOfHotseat);
1659     }
1660 
1661     /**
1662      * Gets the scale of the workspace for the spring-loaded edit state.
1663      */
1664     public float getWorkspaceSpringLoadScale(Context context) {
1665         float scale =
1666                 (getCellLayoutSpringLoadShrunkBottom(context) - getCellLayoutSpringLoadShrunkTop())
1667                         / getCellLayoutHeight();
1668         scale = Math.min(scale, 1f);
1669 
1670         // Reduce scale if next pages would not be visible after scaling the workspace.
1671         int workspaceWidth = availableWidthPx;
1672         float scaledWorkspaceWidth = workspaceWidth * scale;
1673         float maxAvailableWidth = workspaceWidth - (2 * workspaceSpringLoadedMinNextPageVisiblePx);
1674         if (scaledWorkspaceWidth > maxAvailableWidth) {
1675             scale *= maxAvailableWidth / scaledWorkspaceWidth;
1676         }
1677         return scale;
1678     }
1679 
1680     /**
1681      * Gets the width of a single Cell Layout, aka a single panel within a Workspace.
1682      *
1683      * <p>This is the width of a Workspace, less its horizontal padding. Note that two-panel
1684      * layouts have two Cell Layouts per workspace.
1685      */
1686     public int getCellLayoutWidth() {
1687         return (availableWidthPx - getTotalWorkspacePadding().x) / getPanelCount();
1688     }
1689 
1690     /**
1691      * Gets the height of a single Cell Layout, aka a single panel within a Workspace.
1692      *
1693      * <p>This is the height of a Workspace, less its vertical padding.
1694      */
1695     public int getCellLayoutHeight() {
1696         return availableHeightPx - getTotalWorkspacePadding().y;
1697     }
1698 
1699     public Point getTotalWorkspacePadding() {
1700         return new Point(workspacePadding.left + workspacePadding.right,
1701                 workspacePadding.top + workspacePadding.bottom);
1702     }
1703 
1704     /**
1705      * Updates {@link #workspacePadding} as a result of any internal value change to reflect the
1706      * new workspace padding
1707      */
1708     private void updateWorkspacePadding() {
1709         Rect padding = workspacePadding;
1710         if (isVerticalBarLayout()) {
1711             if (mIsResponsiveGrid) {
1712                 padding.top = mResponsiveWorkspaceHeightSpec.getStartPaddingPx();
1713                 padding.bottom = Math.max(0,
1714                         mResponsiveWorkspaceHeightSpec.getEndPaddingPx() - mInsets.bottom);
1715                 if (isSeascape()) {
1716                     padding.left =
1717                             hotseatBarSizePx + mResponsiveWorkspaceWidthSpec.getEndPaddingPx();
1718                     padding.right = mResponsiveWorkspaceWidthSpec.getStartPaddingPx();
1719                 } else {
1720                     padding.left = mResponsiveWorkspaceWidthSpec.getStartPaddingPx();
1721                     padding.right =
1722                             hotseatBarSizePx + mResponsiveWorkspaceWidthSpec.getEndPaddingPx();
1723                 }
1724             } else {
1725                 padding.top = 0;
1726                 padding.bottom = edgeMarginPx;
1727                 if (isSeascape()) {
1728                     padding.left = hotseatBarSizePx;
1729                     padding.right = mHotseatBarEdgePaddingPx;
1730                 } else {
1731                     padding.left = mHotseatBarEdgePaddingPx;
1732                     padding.right = hotseatBarSizePx;
1733                 }
1734             }
1735         } else {
1736             // Pad the bottom of the workspace with hotseat bar
1737             // and leave a bit of space in case a widget go all the way down
1738             int paddingBottom = hotseatBarSizePx + workspaceBottomPadding - mInsets.bottom;
1739             if (!mIsResponsiveGrid) {
1740                 paddingBottom +=
1741                         workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace;
1742             }
1743             int paddingTop = workspaceTopPadding + (mIsScalableGrid ? 0 : edgeMarginPx);
1744             int paddingSide = desiredWorkspaceHorizontalMarginPx;
1745 
1746             padding.set(paddingSide, paddingTop, paddingSide, paddingBottom);
1747         }
1748         insetPadding(workspacePadding, cellLayoutPaddingPx);
1749     }
1750 
1751     private void insetPadding(Rect paddings, Rect insets) {
1752         insets.left = Math.min(insets.left, paddings.left);
1753         paddings.left -= insets.left;
1754 
1755         insets.top = Math.min(insets.top, paddings.top);
1756         paddings.top -= insets.top;
1757 
1758         insets.right = Math.min(insets.right, paddings.right);
1759         paddings.right -= insets.right;
1760 
1761         insets.bottom = Math.min(insets.bottom, paddings.bottom);
1762         paddings.bottom -= insets.bottom;
1763     }
1764 
1765 
1766     /**
1767      * Returns the new border space that should be used between hotseat icons after adjusting it to
1768      * the bubble bar.
1769      *
1770      * <p>If there's no adjustment needed, this method returns {@code 0}.
1771      */
1772     public float getHotseatAdjustedBorderSpaceForBubbleBar(Context context) {
1773         // only need to adjust when QSB is on top of the hotseat.
1774         if (isQsbInline) {
1775             return 0;
1776         }
1777 
1778         // no need to adjust if there's enough space for the bubble bar to the right of the hotseat.
1779         if (getHotseatLayoutPadding(context).right > mBubbleBarSpaceThresholdPx) {
1780             return 0;
1781         }
1782 
1783         // The adjustment is shrinking the hotseat's width by 1 icon on either side.
1784         int iconsWidth =
1785                 iconSizePx * numShownHotseatIcons + hotseatBorderSpace * (numShownHotseatIcons - 1);
1786         int newWidth = iconsWidth - 2 * iconSizePx;
1787         // Evenly space the icons within the boundaries of the new width.
1788         return (float) (newWidth - iconSizePx * numShownHotseatIcons) / (numShownHotseatIcons - 1);
1789     }
1790 
1791     /**
1792      * Returns the padding for hotseat view
1793      */
1794     public Rect getHotseatLayoutPadding(Context context) {
1795         Rect hotseatBarPadding = new Rect();
1796         if (isVerticalBarLayout()) {
1797             // The hotseat icons will be placed in the middle of the hotseat cells.
1798             // Changing the hotseatCellHeightPx is not affecting hotseat icon positions
1799             // in vertical bar layout.
1800             int paddingTop = Math.max((int) (mInsets.top + cellLayoutPaddingPx.top), 0);
1801             int paddingBottom = Math.max((int) (mInsets.bottom + cellLayoutPaddingPx.bottom), 0);
1802 
1803             if (isSeascape()) {
1804                 hotseatBarPadding.set(mInsets.left + mHotseatBarEdgePaddingPx, paddingTop,
1805                         mHotseatBarWorkspaceSpacePx, paddingBottom);
1806             } else {
1807                 hotseatBarPadding.set(mHotseatBarWorkspaceSpacePx, paddingTop,
1808                         mInsets.right + mHotseatBarEdgePaddingPx, paddingBottom);
1809             }
1810         } else if (isTaskbarPresent) {
1811             // Center the QSB vertically with hotseat
1812             int hotseatBarBottomPadding = getHotseatBarBottomPadding();
1813             int hotseatBarTopPadding =
1814                     hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx;
1815 
1816             int hotseatWidth = getHotseatRequiredWidth();
1817             int startSpacing;
1818             int endSpacing;
1819             // Hotseat aligns to the left with nav buttons
1820             if (hotseatBarEndOffset > 0) {
1821                 startSpacing = inlineNavButtonsEndSpacingPx;
1822                 endSpacing = availableWidthPx - hotseatWidth - startSpacing + hotseatBorderSpace;
1823             } else {
1824                 startSpacing = (availableWidthPx - hotseatWidth) / 2;
1825                 endSpacing = startSpacing;
1826             }
1827             startSpacing += getAdditionalQsbSpace();
1828 
1829             hotseatBarPadding.top = hotseatBarTopPadding;
1830             hotseatBarPadding.bottom = hotseatBarBottomPadding;
1831             boolean isRtl = Utilities.isRtl(context.getResources());
1832             if (isRtl) {
1833                 hotseatBarPadding.left = endSpacing;
1834                 hotseatBarPadding.right = startSpacing;
1835             } else {
1836                 hotseatBarPadding.left = startSpacing;
1837                 hotseatBarPadding.right = endSpacing;
1838             }
1839 
1840         } else if (mIsScalableGrid) {
1841             int iconExtraSpacePx = iconSizePx - getIconVisibleSizePx(iconSizePx);
1842             int sideSpacing = (availableWidthPx - (hotseatQsbWidth + iconExtraSpacePx)) / 2;
1843             hotseatBarPadding.set(sideSpacing,
1844                     0,
1845                     sideSpacing,
1846                     getHotseatBarBottomPadding());
1847         } else {
1848             // We want the edges of the hotseat to line up with the edges of the workspace, but the
1849             // icons in the hotseat are a different size, and so don't line up perfectly. To account
1850             // for this, we pad the left and right of the hotseat with half of the difference of a
1851             // workspace cell vs a hotseat cell.
1852             float workspaceCellWidth = (float) widthPx / inv.numColumns;
1853             float hotseatCellWidth = (float) widthPx / numShownHotseatIcons;
1854             int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
1855             hotseatBarPadding.set(
1856                     hotseatAdjustment + workspacePadding.left + cellLayoutPaddingPx.left
1857                             + mInsets.left,
1858                     0,
1859                     hotseatAdjustment + workspacePadding.right + cellLayoutPaddingPx.right
1860                             + mInsets.right,
1861                     getHotseatBarBottomPadding());
1862         }
1863         return hotseatBarPadding;
1864     }
1865 
1866     /** The margin between the edge of all apps and the edge of the first icon. */
1867     public int getAllAppsIconStartMargin(Context context) {
1868         int allAppsSpacing;
1869         if (isVerticalBarLayout()) {
1870             // On phones, the landscape layout uses a different setup.
1871             allAppsSpacing = workspacePadding.left + workspacePadding.right;
1872         } else {
1873             allAppsSpacing =
1874                     allAppsPadding.left + allAppsPadding.right + allAppsLeftRightMargin * 2;
1875         }
1876 
1877         int cellWidth = DeviceProfile.calculateCellWidth(
1878                 availableWidthPx - allAppsSpacing,
1879                 0 /* borderSpace */,
1880                 numShownAllAppsColumns);
1881         int iconAlignmentMargin = (cellWidth - getIconVisibleSizePx(allAppsIconSizePx)) / 2;
1882 
1883         return (Utilities.isRtl(context.getResources()) ? allAppsPadding.right
1884                 : allAppsPadding.left) + iconAlignmentMargin;
1885     }
1886 
1887     /**
1888      * TODO(b/235886078): workaround needed because of this bug
1889      * Icons are 10% larger on XML than their visual size, so remove that extra space to get
1890      * some dimensions correct.
1891      *
1892      * When this bug is resolved this method will no longer be needed and we would be able to
1893      * replace all instances where this method is called with iconSizePx.
1894      */
1895     private int getIconVisibleSizePx(int iconSizePx) {
1896         return Math.round(ICON_VISIBLE_AREA_FACTOR * iconSizePx);
1897     }
1898 
1899     private int getAdditionalQsbSpace() {
1900         return isQsbInline ? hotseatQsbWidth + hotseatBorderSpace : 0;
1901     }
1902 
1903     /**
1904      * Calculate how much space the hotseat needs to be shown completely
1905      */
1906     private int getHotseatRequiredWidth() {
1907         int additionalQsbSpace = getAdditionalQsbSpace();
1908         return iconSizePx * numShownHotseatIcons
1909                 + hotseatBorderSpace * (numShownHotseatIcons - (areNavButtonsInline ? 0 : 1))
1910                 + additionalQsbSpace;
1911     }
1912 
1913     /**
1914      * Returns the number of pixels the QSB is translated from the bottom of the screen.
1915      */
1916     public int getQsbOffsetY() {
1917         if (isQsbInline) {
1918             return getHotseatBarBottomPadding() - ((hotseatQsbHeight - hotseatCellHeightPx) / 2);
1919         } else if (isTaskbarPresent) { // QSB on top
1920             return hotseatBarSizePx - hotseatQsbHeight + hotseatQsbShadowHeight;
1921         } else {
1922             return hotseatBarBottomSpacePx - hotseatQsbShadowHeight;
1923         }
1924     }
1925 
1926     /**
1927      * Returns the number of pixels the hotseat is translated from the bottom of the screen.
1928      */
1929     private int getHotseatBarBottomPadding() {
1930         if (isTaskbarPresent) { // QSB on top or inline
1931             return hotseatBarBottomSpacePx - (Math.abs(hotseatCellHeightPx - iconSizePx) / 2);
1932         } else {
1933             return hotseatBarSizePx - hotseatCellHeightPx;
1934         }
1935     }
1936 
1937     /**
1938      * Returns the number of pixels the taskbar is translated from the bottom of the screen.
1939      */
1940     public int getTaskbarOffsetY() {
1941         int taskbarIconBottomSpace = (taskbarHeight - iconSizePx) / 2;
1942         int launcherIconBottomSpace =
1943                 Math.min((hotseatCellHeightPx - iconSizePx) / 2, gridVisualizationPaddingY);
1944         return getHotseatBarBottomPadding() + launcherIconBottomSpace - taskbarIconBottomSpace;
1945     }
1946 
1947     /** Returns the number of pixels required below OverviewActions. */
1948     public int getOverviewActionsClaimedSpaceBelow() {
1949         return isTaskbarPresent ? mTransientTaskbarClaimedSpace : mInsets.bottom;
1950     }
1951 
1952     /** Gets the space that the overview actions will take, including bottom margin. */
1953     public int getOverviewActionsClaimedSpace() {
1954         int overviewActionsSpace = isTablet && Flags.enableGridOnlyOverview()
1955                 ? 0
1956                 : (overviewActionsTopMarginPx + overviewActionsHeight);
1957         return overviewActionsSpace + getOverviewActionsClaimedSpaceBelow();
1958     }
1959 
1960     /**
1961      * Takes the View and return the scales of width and height depending on the DeviceProfile
1962      * specifications
1963      *
1964      * @param itemInfo The tag of the widget view
1965      * @return A PointF instance with the x set to be the scale of width, and y being the scale of
1966      * height
1967      */
1968     @NonNull
1969     public PointF getAppWidgetScale(@Nullable final ItemInfo itemInfo) {
1970         return mViewScaleProvider.getScaleFromItemInfo(itemInfo);
1971     }
1972 
1973     /**
1974      * @return the bounds for which the open folders should be contained within
1975      */
1976     public Rect getAbsoluteOpenFolderBounds() {
1977         if (isVerticalBarLayout()) {
1978             // Folders should only appear right of the drop target bar and left of the hotseat
1979             return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx,
1980                     mInsets.top,
1981                     mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx,
1982                     mInsets.top + availableHeightPx);
1983         } else {
1984             // Folders should only appear below the drop target bar and above the hotseat
1985             int hotseatTop = isTaskbarPresent ? taskbarHeight : hotseatBarSizePx;
1986             return new Rect(mInsets.left + edgeMarginPx,
1987                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
1988                     mInsets.left + availableWidthPx - edgeMarginPx,
1989                     mInsets.top + availableHeightPx - hotseatTop
1990                             - workspacePageIndicatorHeight - edgeMarginPx);
1991         }
1992     }
1993 
1994     public static int calculateCellWidth(int width, int borderSpacing, int countX) {
1995         return (width - ((countX - 1) * borderSpacing)) / countX;
1996     }
1997 
1998     public static int calculateCellHeight(int height, int borderSpacing, int countY) {
1999         return (height - ((countY - 1) * borderSpacing)) / countY;
2000     }
2001 
2002     /**
2003      * When {@code true}, the device is in landscape mode and the hotseat is on the right column.
2004      * When {@code false}, either device is in portrait mode or the device is in landscape mode and
2005      * the hotseat is on the bottom row.
2006      */
2007     public boolean isVerticalBarLayout() {
2008         return isLandscape && transposeLayoutWithOrientation;
2009     }
2010 
2011     /**
2012      * Updates orientation information and returns true if it has changed from the previous value.
2013      */
2014     public boolean updateIsSeascape(Context context) {
2015         if (isVerticalBarLayout()) {
2016             boolean isSeascape = DisplayController.INSTANCE.get(context)
2017                     .getInfo().rotation == Surface.ROTATION_270;
2018             if (mIsSeascape != isSeascape) {
2019                 mIsSeascape = isSeascape;
2020                 // Hotseat changing sides requires updating workspace left/right paddings
2021                 updateWorkspacePadding();
2022                 return true;
2023             }
2024         }
2025         return false;
2026     }
2027 
2028     public boolean isSeascape() {
2029         return isVerticalBarLayout() && mIsSeascape;
2030     }
2031 
2032     public boolean shouldFadeAdjacentWorkspaceScreens() {
2033         return isVerticalBarLayout();
2034     }
2035 
2036     public int getCellContentHeight(@ContainerType int containerType) {
2037         switch (containerType) {
2038             case CellLayout.WORKSPACE:
2039                 return cellHeightPx;
2040             case CellLayout.FOLDER:
2041                 return folderCellHeightPx;
2042             case CellLayout.HOTSEAT:
2043                 // The hotseat is the only container where the cell height is going to be
2044                 // different from the content within that cell.
2045                 return iconSizePx;
2046             default:
2047                 // ??
2048                 return 0;
2049         }
2050     }
2051 
2052     private String pxToDpStr(String name, float value) {
2053         return "\t" + name + ": " + value + "px (" + dpiFromPx(value, mMetrics.densityDpi) + "dp)";
2054     }
2055 
2056     private String dpPointFToString(String name, PointF value) {
2057         return String.format(Locale.ENGLISH, "\t%s: PointF(%.1f, %.1f)dp", name, value.x, value.y);
2058     }
2059 
2060     /** Dumps various DeviceProfile variables to the specified writer. */
2061     public void dump(Context context, String prefix, PrintWriter writer) {
2062         writer.println(prefix + "DeviceProfile:");
2063         writer.println(prefix + "\t1 dp = " + mMetrics.density + " px");
2064 
2065         writer.println(prefix + "\tisTablet:" + isTablet);
2066         writer.println(prefix + "\tisPhone:" + isPhone);
2067         writer.println(prefix + "\ttransposeLayoutWithOrientation:"
2068                 + transposeLayoutWithOrientation);
2069         writer.println(prefix + "\tisGestureMode:" + isGestureMode);
2070 
2071         writer.println(prefix + "\tisLandscape:" + isLandscape);
2072         writer.println(prefix + "\tisMultiWindowMode:" + isMultiWindowMode);
2073         writer.println(prefix + "\tisTwoPanels:" + isTwoPanels);
2074         writer.println(prefix + "\tisLeftRightSplit:" + isLeftRightSplit);
2075 
2076         writer.println(prefix + pxToDpStr("windowX", windowX));
2077         writer.println(prefix + pxToDpStr("windowY", windowY));
2078         writer.println(prefix + pxToDpStr("widthPx", widthPx));
2079         writer.println(prefix + pxToDpStr("heightPx", heightPx));
2080         writer.println(prefix + pxToDpStr("availableWidthPx", availableWidthPx));
2081         writer.println(prefix + pxToDpStr("availableHeightPx", availableHeightPx));
2082         writer.println(prefix + pxToDpStr("mInsets.left", mInsets.left));
2083         writer.println(prefix + pxToDpStr("mInsets.top", mInsets.top));
2084         writer.println(prefix + pxToDpStr("mInsets.right", mInsets.right));
2085         writer.println(prefix + pxToDpStr("mInsets.bottom", mInsets.bottom));
2086 
2087         writer.println(prefix + "\taspectRatio:" + aspectRatio);
2088 
2089         writer.println(prefix + "\tisResponsiveGrid:" + mIsResponsiveGrid);
2090         writer.println(prefix + "\tisScalableGrid:" + mIsScalableGrid);
2091 
2092         writer.println(prefix + "\tinv.numRows: " + inv.numRows);
2093         writer.println(prefix + "\tinv.numColumns: " + inv.numColumns);
2094         writer.println(prefix + "\tinv.numSearchContainerColumns: "
2095                 + inv.numSearchContainerColumns);
2096 
2097         writer.println(prefix + dpPointFToString("minCellSize", inv.minCellSize[mTypeIndex]));
2098 
2099         writer.println(prefix + pxToDpStr("cellWidthPx", cellWidthPx));
2100         writer.println(prefix + pxToDpStr("cellHeightPx", cellHeightPx));
2101 
2102         writer.println(prefix + pxToDpStr("getCellSize().x", getCellSize().x));
2103         writer.println(prefix + pxToDpStr("getCellSize().y", getCellSize().y));
2104 
2105         writer.println(prefix + pxToDpStr("cellLayoutBorderSpacePx Horizontal",
2106                 cellLayoutBorderSpacePx.x));
2107         writer.println(prefix + pxToDpStr("cellLayoutBorderSpacePx Vertical",
2108                 cellLayoutBorderSpacePx.y));
2109         writer.println(
2110                 prefix + pxToDpStr("cellLayoutPaddingPx.left", cellLayoutPaddingPx.left));
2111         writer.println(
2112                 prefix + pxToDpStr("cellLayoutPaddingPx.top", cellLayoutPaddingPx.top));
2113         writer.println(
2114                 prefix + pxToDpStr("cellLayoutPaddingPx.right", cellLayoutPaddingPx.right));
2115         writer.println(
2116                 prefix + pxToDpStr("cellLayoutPaddingPx.bottom", cellLayoutPaddingPx.bottom));
2117 
2118         writer.println(prefix + pxToDpStr("iconSizePx", iconSizePx));
2119         writer.println(prefix + pxToDpStr("iconTextSizePx", iconTextSizePx));
2120         writer.println(prefix + pxToDpStr("iconDrawablePaddingPx", iconDrawablePaddingPx));
2121 
2122         writer.println(prefix + "\tnumFolderRows: " + numFolderRows);
2123         writer.println(prefix + "\tnumFolderColumns: " + numFolderColumns);
2124         writer.println(prefix + pxToDpStr("folderCellWidthPx", folderCellWidthPx));
2125         writer.println(prefix + pxToDpStr("folderCellHeightPx", folderCellHeightPx));
2126         writer.println(prefix + pxToDpStr("folderChildIconSizePx", folderChildIconSizePx));
2127         writer.println(prefix + pxToDpStr("folderChildTextSizePx", folderChildTextSizePx));
2128         writer.println(prefix + pxToDpStr("folderChildDrawablePaddingPx",
2129                 folderChildDrawablePaddingPx));
2130         writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx.x",
2131                 folderCellLayoutBorderSpacePx.x));
2132         writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx.y",
2133                 folderCellLayoutBorderSpacePx.y));
2134         writer.println(prefix + pxToDpStr("folderContentPaddingLeftRight",
2135                 folderContentPaddingLeftRight));
2136         writer.println(prefix + pxToDpStr("folderTopPadding", folderContentPaddingTop));
2137         writer.println(prefix + pxToDpStr("folderFooterHeight", folderFooterHeightPx));
2138 
2139         writer.println(prefix + pxToDpStr("bottomSheetTopPadding", bottomSheetTopPadding));
2140         writer.println(prefix + "\tbottomSheetOpenDuration: " + bottomSheetOpenDuration);
2141         writer.println(prefix + "\tbottomSheetCloseDuration: " + bottomSheetCloseDuration);
2142         writer.println(prefix + "\tbottomSheetWorkspaceScale: " + bottomSheetWorkspaceScale);
2143         writer.println(prefix + "\tbottomSheetDepth: " + bottomSheetDepth);
2144 
2145         writer.println(prefix + pxToDpStr("allAppsShiftRange", allAppsShiftRange));
2146         writer.println(prefix + "\tallAppsOpenDuration: " + allAppsOpenDuration);
2147         writer.println(prefix + "\tallAppsCloseDuration: " + allAppsCloseDuration);
2148         writer.println(prefix + pxToDpStr("allAppsIconSizePx", allAppsIconSizePx));
2149         writer.println(prefix + pxToDpStr("allAppsIconTextSizePx", allAppsIconTextSizePx));
2150         writer.println(prefix + pxToDpStr("allAppsIconDrawablePaddingPx",
2151                 allAppsIconDrawablePaddingPx));
2152         writer.println(prefix + pxToDpStr("allAppsCellHeightPx", allAppsCellHeightPx));
2153         writer.println(prefix + pxToDpStr("allAppsCellWidthPx", allAppsCellWidthPx));
2154         writer.println(prefix + pxToDpStr("allAppsBorderSpacePxX", allAppsBorderSpacePx.x));
2155         writer.println(prefix + pxToDpStr("allAppsBorderSpacePxY", allAppsBorderSpacePx.y));
2156         writer.println(prefix + "\tnumShownAllAppsColumns: " + numShownAllAppsColumns);
2157         writer.println(prefix + pxToDpStr("allAppsPadding.top", allAppsPadding.top));
2158         writer.println(prefix + pxToDpStr("allAppsPadding.left", allAppsPadding.left));
2159         writer.println(prefix + pxToDpStr("allAppsPadding.right", allAppsPadding.right));
2160         writer.println(prefix + pxToDpStr("allAppsLeftRightMargin", allAppsLeftRightMargin));
2161 
2162         writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx));
2163         writer.println(prefix + "\tmHotseatColumnSpan: " + mHotseatColumnSpan);
2164         writer.println(prefix + pxToDpStr("mHotseatWidthPx", mHotseatWidthPx));
2165         writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx));
2166         writer.println(prefix + pxToDpStr("hotseatBarBottomSpacePx", hotseatBarBottomSpacePx));
2167         writer.println(prefix + pxToDpStr("mHotseatBarEdgePaddingPx",
2168                 mHotseatBarEdgePaddingPx));
2169         writer.println(prefix + pxToDpStr("mHotseatBarWorkspaceSpacePx",
2170                 mHotseatBarWorkspaceSpacePx));
2171         writer.println(prefix + pxToDpStr("hotseatBarEndOffset", hotseatBarEndOffset));
2172         writer.println(prefix + pxToDpStr("hotseatQsbSpace", hotseatQsbSpace));
2173         writer.println(prefix + pxToDpStr("hotseatQsbHeight", hotseatQsbHeight));
2174         writer.println(prefix + pxToDpStr("springLoadedHotseatBarTopMarginPx",
2175                 springLoadedHotseatBarTopMarginPx));
2176         Rect hotseatLayoutPadding = getHotseatLayoutPadding(context);
2177         writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).top",
2178                 hotseatLayoutPadding.top));
2179         writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).bottom",
2180                 hotseatLayoutPadding.bottom));
2181         writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).left",
2182                 hotseatLayoutPadding.left));
2183         writer.println(prefix + pxToDpStr("getHotseatLayoutPadding(context).right",
2184                 hotseatLayoutPadding.right));
2185         writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons);
2186         writer.println(prefix + pxToDpStr("hotseatBorderSpace", hotseatBorderSpace));
2187         writer.println(prefix + "\tisQsbInline: " + isQsbInline);
2188         writer.println(prefix + pxToDpStr("hotseatQsbWidth", hotseatQsbWidth));
2189 
2190         writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent);
2191         writer.println(prefix + "\tisTaskbarPresentInApps:" + isTaskbarPresentInApps);
2192         writer.println(prefix + pxToDpStr("taskbarHeight", taskbarHeight));
2193         writer.println(prefix + pxToDpStr("stashedTaskbarHeight", stashedTaskbarHeight));
2194         writer.println(prefix + pxToDpStr("taskbarBottomMargin", taskbarBottomMargin));
2195         writer.println(prefix + pxToDpStr("taskbarIconSize", taskbarIconSize));
2196 
2197         writer.println(prefix + pxToDpStr("desiredWorkspaceHorizontalMarginPx",
2198                 desiredWorkspaceHorizontalMarginPx));
2199         writer.println(prefix + pxToDpStr("workspacePadding.left", workspacePadding.left));
2200         writer.println(prefix + pxToDpStr("workspacePadding.top", workspacePadding.top));
2201         writer.println(prefix + pxToDpStr("workspacePadding.right", workspacePadding.right));
2202         writer.println(prefix + pxToDpStr("workspacePadding.bottom", workspacePadding.bottom));
2203 
2204         writer.println(prefix + pxToDpStr("iconScale", iconScale));
2205         writer.println(prefix + pxToDpStr("cellScaleToFit ", cellScaleToFit));
2206         writer.println(prefix + pxToDpStr("extraSpace", extraSpace));
2207         writer.println(prefix + pxToDpStr("unscaled extraSpace", extraSpace / iconScale));
2208 
2209         writer.println(prefix + pxToDpStr("maxEmptySpace", maxEmptySpace));
2210         writer.println(prefix + pxToDpStr("workspaceTopPadding", workspaceTopPadding));
2211         writer.println(prefix + pxToDpStr("workspaceBottomPadding", workspaceBottomPadding));
2212 
2213         writer.println(prefix + pxToDpStr("overviewTaskMarginPx", overviewTaskMarginPx));
2214         writer.println(prefix + pxToDpStr("overviewTaskIconSizePx", overviewTaskIconSizePx));
2215         writer.println(prefix + pxToDpStr("overviewTaskIconDrawableSizePx",
2216                 overviewTaskIconDrawableSizePx));
2217         writer.println(prefix + pxToDpStr("overviewTaskIconDrawableSizeGridPx",
2218                 overviewTaskIconDrawableSizeGridPx));
2219         writer.println(prefix + pxToDpStr("overviewTaskThumbnailTopMarginPx",
2220                 overviewTaskThumbnailTopMarginPx));
2221         writer.println(prefix + pxToDpStr("overviewActionsTopMarginPx",
2222                 overviewActionsTopMarginPx));
2223         writer.println(prefix + pxToDpStr("overviewActionsHeight",
2224                 overviewActionsHeight));
2225         writer.println(prefix + pxToDpStr("overviewActionsClaimedSpaceBelow",
2226                 getOverviewActionsClaimedSpaceBelow()));
2227         writer.println(prefix + pxToDpStr("overviewActionsButtonSpacing",
2228                 overviewActionsButtonSpacing));
2229         writer.println(prefix + pxToDpStr("overviewPageSpacing", overviewPageSpacing));
2230         writer.println(prefix + pxToDpStr("overviewRowSpacing", overviewRowSpacing));
2231         writer.println(prefix + pxToDpStr("overviewGridSideMargin", overviewGridSideMargin));
2232 
2233         writer.println(prefix + pxToDpStr("dropTargetBarTopMarginPx", dropTargetBarTopMarginPx));
2234         writer.println(prefix + pxToDpStr("dropTargetBarSizePx", dropTargetBarSizePx));
2235         writer.println(
2236                 prefix + pxToDpStr("dropTargetBarBottomMarginPx", dropTargetBarBottomMarginPx));
2237 
2238         writer.println(prefix + pxToDpStr("getCellLayoutSpringLoadShrunkTop()",
2239                 getCellLayoutSpringLoadShrunkTop()));
2240         writer.println(prefix + pxToDpStr("getCellLayoutSpringLoadShrunkBottom()",
2241                 getCellLayoutSpringLoadShrunkBottom(context)));
2242         writer.println(prefix + pxToDpStr("workspaceSpringLoadedMinNextPageVisiblePx",
2243                 workspaceSpringLoadedMinNextPageVisiblePx));
2244         writer.println(prefix + pxToDpStr("getWorkspaceSpringLoadScale()",
2245                 getWorkspaceSpringLoadScale(context)));
2246         writer.println(prefix + pxToDpStr("getCellLayoutHeight()", getCellLayoutHeight()));
2247         writer.println(prefix + pxToDpStr("getCellLayoutWidth()", getCellLayoutWidth()));
2248         if (mIsResponsiveGrid) {
2249             writer.println(prefix + "\tmResponsiveWorkspaceHeightSpec:"
2250                     + mResponsiveWorkspaceHeightSpec.toString());
2251             writer.println(prefix + "\tmResponsiveWorkspaceWidthSpec:"
2252                     + mResponsiveWorkspaceWidthSpec.toString());
2253             writer.println(prefix + "\tmResponsiveAllAppsHeightSpec:"
2254                     + mResponsiveAllAppsHeightSpec.toString());
2255             writer.println(prefix + "\tmResponsiveAllAppsWidthSpec:"
2256                     + mResponsiveAllAppsWidthSpec.toString());
2257             writer.println(prefix + "\tmResponsiveFolderHeightSpec:" + mResponsiveFolderHeightSpec);
2258             writer.println(prefix + "\tmResponsiveFolderWidthSpec:" + mResponsiveFolderWidthSpec);
2259             writer.println(prefix + "\tmResponsiveHotseatSpec:" + mResponsiveHotseatSpec);
2260             writer.println(prefix + "\tmResponsiveWorkspaceCellSpec:"
2261                     + mResponsiveWorkspaceCellSpec);
2262             writer.println(prefix + "\tmResponsiveAllAppsCellSpec:" + mResponsiveAllAppsCellSpec);
2263         }
2264     }
2265 
2266     /** Returns a reduced representation of this DeviceProfile. */
2267     public String toSmallString() {
2268         return "isTablet:" + isTablet + ", "
2269                 + "isMultiDisplay:" + isMultiDisplay + ", "
2270                 + "widthPx:" + widthPx + ", "
2271                 + "heightPx:" + heightPx + ", "
2272                 + "insets:" + mInsets + ", "
2273                 + "rotationHint:" + rotationHint;
2274     }
2275 
2276     private static Context getContext(Context c, Info info, int orientation, WindowBounds bounds) {
2277         Configuration config = new Configuration(c.getResources().getConfiguration());
2278         config.orientation = orientation;
2279         config.densityDpi = info.getDensityDpi();
2280         config.smallestScreenWidthDp = (int) info.smallestSizeDp(bounds);
2281         return c.createConfigurationContext(config);
2282     }
2283 
2284     /**
2285      * Callback when a component changes the DeviceProfile associated with it, as a result of
2286      * configuration change
2287      */
2288     public interface OnDeviceProfileChangeListener {
2289 
2290         /**
2291          * Called when the device profile is reassigned. Note that for layout and measurements, it
2292          * is sufficient to listen for inset changes. Use this callback when you need to perform
2293          * a one time operation.
2294          */
2295         void onDeviceProfileChanged(DeviceProfile dp);
2296     }
2297 
2298     /**
2299      * Handler that deals with ItemInfo of the views for the DeviceProfile
2300      */
2301     @FunctionalInterface
2302     public interface ViewScaleProvider {
2303         /**
2304          * Get the scales from the view
2305          *
2306          * @param itemInfo The tag of the widget view
2307          * @return PointF instance containing the scale information, or null if using the default
2308          * app widget scale of this device profile.
2309          */
2310         @NonNull
2311         PointF getScaleFromItemInfo(@Nullable ItemInfo itemInfo);
2312     }
2313 
2314     public static class Builder {
2315         private Context mContext;
2316         private InvariantDeviceProfile mInv;
2317         private Info mInfo;
2318 
2319         private WindowBounds mWindowBounds;
2320         private boolean mIsMultiDisplay;
2321 
2322         private boolean mIsMultiWindowMode = false;
2323         private Boolean mTransposeLayoutWithOrientation;
2324         private Boolean mIsGestureMode;
2325         private ViewScaleProvider mViewScaleProvider = null;
2326 
2327         private SparseArray<DotRenderer> mDotRendererCache;
2328 
2329         private Consumer<DeviceProfile> mOverrideProvider;
2330 
2331         private boolean mIsTransientTaskbar;
2332 
2333         public Builder(Context context, InvariantDeviceProfile inv, Info info) {
2334             mContext = context;
2335             mInv = inv;
2336             mInfo = info;
2337             mIsTransientTaskbar = info.isTransientTaskbar();
2338         }
2339 
2340         public Builder setMultiWindowMode(boolean isMultiWindowMode) {
2341             mIsMultiWindowMode = isMultiWindowMode;
2342             return this;
2343         }
2344 
2345         public Builder setIsMultiDisplay(boolean isMultiDisplay) {
2346             mIsMultiDisplay = isMultiDisplay;
2347             return this;
2348         }
2349 
2350         public Builder setDotRendererCache(SparseArray<DotRenderer> dotRendererCache) {
2351             mDotRendererCache = dotRendererCache;
2352             return this;
2353         }
2354 
2355         public Builder setWindowBounds(WindowBounds bounds) {
2356             mWindowBounds = bounds;
2357             return this;
2358         }
2359 
2360         public Builder setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation) {
2361             mTransposeLayoutWithOrientation = transposeLayoutWithOrientation;
2362             return this;
2363         }
2364 
2365         public Builder setGestureMode(boolean isGestureMode) {
2366             mIsGestureMode = isGestureMode;
2367             return this;
2368         }
2369 
2370         public Builder withDimensionsOverride(Consumer<DeviceProfile> overrideProvider) {
2371             mOverrideProvider = overrideProvider;
2372             return this;
2373         }
2374 
2375         /**
2376          * Set the viewScaleProvider for the builder
2377          *
2378          * @param viewScaleProvider The viewScaleProvider to be set for the
2379          *                          DeviceProfile
2380          * @return This builder
2381          */
2382         @NonNull
2383         public Builder setViewScaleProvider(@Nullable ViewScaleProvider viewScaleProvider) {
2384             mViewScaleProvider = viewScaleProvider;
2385             return this;
2386         }
2387 
2388         /**
2389          * Set the isTransientTaskbar for the builder
2390          * @return This Builder
2391          */
2392         public Builder setIsTransientTaskbar(boolean isTransientTaskbar) {
2393             mIsTransientTaskbar = isTransientTaskbar;
2394             return this;
2395         }
2396 
2397         public DeviceProfile build() {
2398             if (mWindowBounds == null) {
2399                 throw new IllegalArgumentException("Window bounds not set");
2400             }
2401             if (mTransposeLayoutWithOrientation == null) {
2402                 mTransposeLayoutWithOrientation = !mInfo.isTablet(mWindowBounds);
2403             }
2404             if (mIsGestureMode == null) {
2405                 mIsGestureMode = mInfo.getNavigationMode().hasGestures;
2406             }
2407             if (mDotRendererCache == null) {
2408                 mDotRendererCache = new SparseArray<>();
2409             }
2410             if (mViewScaleProvider == null) {
2411                 mViewScaleProvider = DEFAULT_PROVIDER;
2412             }
2413             if (mOverrideProvider == null) {
2414                 mOverrideProvider = DEFAULT_DIMENSION_PROVIDER;
2415             }
2416             return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, mDotRendererCache,
2417                     mIsMultiWindowMode, mTransposeLayoutWithOrientation, mIsMultiDisplay,
2418                     mIsGestureMode, mViewScaleProvider, mOverrideProvider, mIsTransientTaskbar);
2419         }
2420     }
2421 }
2422