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