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