1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3;
18 
19 import static com.android.launcher3.ResourceUtils.pxFromDp;
20 import static com.android.launcher3.Utilities.dpiFromPx;
21 import static com.android.launcher3.Utilities.pxFromSp;
22 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
23 
24 import android.annotation.SuppressLint;
25 import android.content.Context;
26 import android.content.res.Configuration;
27 import android.content.res.Resources;
28 import android.graphics.Path;
29 import android.graphics.Point;
30 import android.graphics.PointF;
31 import android.graphics.Rect;
32 import android.util.DisplayMetrics;
33 import android.view.Surface;
34 
35 import com.android.launcher3.CellLayout.ContainerType;
36 import com.android.launcher3.DevicePaddings.DevicePadding;
37 import com.android.launcher3.config.FeatureFlags;
38 import com.android.launcher3.icons.DotRenderer;
39 import com.android.launcher3.icons.GraphicsUtils;
40 import com.android.launcher3.icons.IconNormalizer;
41 import com.android.launcher3.uioverrides.ApiWrapper;
42 import com.android.launcher3.util.DisplayController;
43 import com.android.launcher3.util.DisplayController.Info;
44 import com.android.launcher3.util.WindowBounds;
45 
46 import java.io.PrintWriter;
47 
48 @SuppressLint("NewApi")
49 public class DeviceProfile {
50 
51     private static final int DEFAULT_DOT_SIZE = 100;
52     // Ratio of empty space, qsb should take up to appear visually centered.
53     private static final float QSB_CENTER_FACTOR = .325f;
54 
55     public final InvariantDeviceProfile inv;
56     private final Info mInfo;
57     private final DisplayMetrics mMetrics;
58 
59     // Device properties
60     public final boolean isTablet;
61     public final boolean isPhone;
62     public final boolean transposeLayoutWithOrientation;
63     public final boolean isTwoPanels;
64 
65     // Device properties in current orientation
66     public final boolean isLandscape;
67     public final boolean isMultiWindowMode;
68 
69     public final int windowX;
70     public final int windowY;
71     public final int widthPx;
72     public final int heightPx;
73     public final int availableWidthPx;
74     public final int availableHeightPx;
75 
76     public final float aspectRatio;
77 
78     public final boolean isScalableGrid;
79     private final int mTypeIndex;
80 
81     /**
82      * The maximum amount of left/right workspace padding as a percentage of the screen width.
83      * To be clear, this means that up to 7% of the screen width can be used as left padding, and
84      * 7% of the screen width can be used as right padding.
85      */
86     private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f;
87 
88     private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
89     private static final float TALLER_DEVICE_ASPECT_RATIO_THRESHOLD = 2.15f;
90     private static final float TALL_DEVICE_EXTRA_SPACE_THRESHOLD_DP = 252;
91     private static final float TALL_DEVICE_MORE_EXTRA_SPACE_THRESHOLD_DP = 268;
92 
93     // To evenly space the icons, increase the left/right margins for tablets in portrait mode.
94     private static final int PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER = 4;
95 
96     // Workspace
97     public final int desiredWorkspaceHorizontalMarginOriginalPx;
98     public int desiredWorkspaceHorizontalMarginPx;
99     public Point cellLayoutBorderSpaceOriginalPx;
100     public Point cellLayoutBorderSpacePx;
101     public final int cellLayoutPaddingLeftRightPx;
102     public final int cellLayoutBottomPaddingPx;
103     public final int edgeMarginPx;
104     public float workspaceSpringLoadShrinkFactor;
105     public final int workspaceSpringLoadedBottomSpace;
106 
107     private final int extraSpace;
108     public int workspaceTopPadding;
109     public int workspaceBottomPadding;
110     public int extraHotseatBottomPadding;
111 
112     // Workspace page indicator
113     public final int workspacePageIndicatorHeight;
114     private final int mWorkspacePageIndicatorOverlapWorkspace;
115 
116     // Workspace icons
117     public float iconScale;
118     public int iconSizePx;
119     public int iconTextSizePx;
120     public int iconDrawablePaddingPx;
121     public int iconDrawablePaddingOriginalPx;
122 
123     public float cellScaleToFit;
124     public int cellWidthPx;
125     public int cellHeightPx;
126     public int workspaceCellPaddingXPx;
127 
128     public int cellYPaddingPx;
129 
130     // Folder
131     public float folderLabelTextScale;
132     public int folderLabelTextSizePx;
133     public int folderIconSizePx;
134     public int folderIconOffsetYPx;
135 
136     // Folder content
137     public Point folderCellLayoutBorderSpacePx;
138     public int folderCellLayoutBorderSpaceOriginalPx;
139     public int folderContentPaddingLeftRight;
140     public int folderContentPaddingTop;
141 
142     // Folder cell
143     public int folderCellWidthPx;
144     public int folderCellHeightPx;
145 
146     // Folder child
147     public int folderChildIconSizePx;
148     public int folderChildTextSizePx;
149     public int folderChildDrawablePaddingPx;
150 
151     // Hotseat
152     public int hotseatBarSizeExtraSpacePx;
153     public final int numShownHotseatIcons;
154     public int hotseatCellHeightPx;
155     private final int hotseatExtraVerticalSize;
156     // In portrait: size = height, in landscape: size = width
157     public int hotseatBarSizePx;
158     public int hotseatBarTopPaddingPx;
159     public final int hotseatBarBottomPaddingPx;
160     // Start is the side next to the nav bar, end is the side next to the workspace
161     public final int hotseatBarSidePaddingStartPx;
162     public final int hotseatBarSidePaddingEndPx;
163     public final int hotseatQsbHeight;
164 
165     public final float qsbBottomMarginOriginalPx;
166     public int qsbBottomMarginPx;
167 
168     // All apps
169     public Point allAppsCellSpacePx;
170     public int allAppsOpenVerticalTranslate;
171     public int allAppsCellHeightPx;
172     public int allAppsCellWidthPx;
173     public int allAppsIconSizePx;
174     public int allAppsIconDrawablePaddingPx;
175     public int allAppsLeftRightPadding;
176     public final int numShownAllAppsColumns;
177     public float allAppsIconTextSizePx;
178 
179     // Overview
180     public final boolean overviewShowAsGrid;
181     public int overviewTaskMarginPx;
182     public int overviewTaskMarginGridPx;
183     public int overviewTaskIconSizePx;
184     public int overviewTaskIconDrawableSizePx;
185     public int overviewTaskIconDrawableSizeGridPx;
186     public int overviewTaskThumbnailTopMarginPx;
187     public final int overviewActionsMarginThreeButtonPx;
188     public final int overviewActionsTopMarginGesturePx;
189     public final int overviewActionsBottomMarginGesturePx;
190     public final int overviewActionsButtonSpacing;
191     public int overviewPageSpacing;
192     public int overviewRowSpacing;
193     public int overviewGridSideMargin;
194 
195     // Widgets
196     public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
197 
198     // Drop Target
199     public int dropTargetBarSizePx;
200     public int dropTargetDragPaddingPx;
201     public int dropTargetTextSizePx;
202 
203     // Insets
204     private final Rect mInsets = new Rect();
205     public final Rect workspacePadding = new Rect();
206     private final Rect mHotseatPadding = new Rect();
207     // When true, nav bar is on the left side of the screen.
208     private boolean mIsSeascape;
209 
210     // Notification dots
211     public DotRenderer mDotRendererWorkSpace;
212     public DotRenderer mDotRendererAllApps;
213 
214     // Taskbar
215     public boolean isTaskbarPresent;
216     // Whether Taskbar will inset the bottom of apps by taskbarSize.
217     public boolean isTaskbarPresentInApps;
218     public int taskbarSize;
219 
220     // DragController
221     public int flingToDeleteThresholdVelocity;
222 
223     /** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */
DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds, boolean isMultiWindowMode, boolean transposeLayoutWithOrientation, boolean useTwoPanels)224     DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
225             boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
226             boolean useTwoPanels) {
227 
228         this.inv = inv;
229         this.isLandscape = windowBounds.isLandscape();
230         this.isMultiWindowMode = isMultiWindowMode;
231         this.transposeLayoutWithOrientation = transposeLayoutWithOrientation;
232         windowX = windowBounds.bounds.left;
233         windowY = windowBounds.bounds.top;
234 
235         isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
236 
237         // Determine sizes.
238         widthPx = windowBounds.bounds.width();
239         heightPx = windowBounds.bounds.height();
240         availableWidthPx = windowBounds.availableSize.x;
241         availableHeightPx = windowBounds.availableSize.y;
242 
243         mInfo = info;
244         isTablet = info.isTablet(windowBounds);
245         isPhone = !isTablet;
246         isTwoPanels = isTablet && useTwoPanels;
247 
248         aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
249         boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
250 
251         // Some more constants
252         context = getContext(context, info, isVerticalBarLayout()
253                 ? Configuration.ORIENTATION_LANDSCAPE
254                 : Configuration.ORIENTATION_PORTRAIT);
255         mMetrics = context.getResources().getDisplayMetrics();
256         final Resources res = context.getResources();
257 
258         if (isTwoPanels) {
259             if (isLandscape) {
260                 mTypeIndex = InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
261             } else {
262                 mTypeIndex = InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
263             }
264         } else {
265             if (isLandscape) {
266                 mTypeIndex = InvariantDeviceProfile.INDEX_LANDSCAPE;
267             } else {
268                 mTypeIndex = InvariantDeviceProfile.INDEX_DEFAULT;
269             }
270         }
271 
272         hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height);
273         isTaskbarPresent = isTablet && ApiWrapper.TASKBAR_DRAWN_IN_PROCESS
274                 && FeatureFlags.ENABLE_TASKBAR.get();
275         if (isTaskbarPresent) {
276             taskbarSize = res.getDimensionPixelSize(R.dimen.taskbar_size);
277         }
278 
279         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
280 
281         desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
282         desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx;
283 
284         allAppsOpenVerticalTranslate = res.getDimensionPixelSize(
285                 R.dimen.all_apps_open_vertical_translate);
286 
287         folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
288         folderContentPaddingLeftRight =
289                 res.getDimensionPixelSize(R.dimen.folder_content_padding_left_right);
290         folderContentPaddingTop = res.getDimensionPixelSize(R.dimen.folder_content_padding_top);
291 
292         cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv);
293         allAppsCellSpacePx = new Point(
294                 pxFromDp(inv.borderSpaces[InvariantDeviceProfile.INDEX_ALL_APPS].x, mMetrics, 1f),
295                 pxFromDp(inv.borderSpaces[InvariantDeviceProfile.INDEX_ALL_APPS].y, mMetrics, 1f));
296         cellLayoutBorderSpaceOriginalPx = new Point(cellLayoutBorderSpacePx);
297         folderCellLayoutBorderSpaceOriginalPx = pxFromDp(inv.folderBorderSpace, mMetrics, 1f);
298         folderCellLayoutBorderSpacePx = new Point(folderCellLayoutBorderSpaceOriginalPx,
299                 folderCellLayoutBorderSpaceOriginalPx);
300 
301         int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
302                 ? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
303         int cellLayoutPadding = isScalableGrid
304                 ? 0
305                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
306 
307         if (isTwoPanels) {
308             cellLayoutPaddingLeftRightPx = 0;
309             cellLayoutBottomPaddingPx = 0;
310         } else if (isLandscape) {
311             cellLayoutPaddingLeftRightPx = 0;
312             cellLayoutBottomPaddingPx = cellLayoutPadding;
313         } else {
314             cellLayoutPaddingLeftRightPx = cellLayoutPaddingLeftRightMultiplier * cellLayoutPadding;
315             cellLayoutBottomPaddingPx = 0;
316         }
317 
318         workspacePageIndicatorHeight = res.getDimensionPixelSize(
319                 R.dimen.workspace_page_indicator_height);
320         mWorkspacePageIndicatorOverlapWorkspace =
321                 res.getDimensionPixelSize(R.dimen.workspace_page_indicator_overlap_workspace);
322 
323         iconDrawablePaddingOriginalPx =
324                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
325 
326         dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
327         dropTargetDragPaddingPx = res.getDimensionPixelSize(R.dimen.drop_target_drag_padding);
328         dropTargetTextSizePx = res.getDimensionPixelSize(R.dimen.drop_target_text_size);
329 
330         workspaceSpringLoadedBottomSpace =
331                 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
332 
333         workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
334 
335         numShownHotseatIcons =
336                 isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons;
337         numShownAllAppsColumns =
338                 isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns;
339         hotseatBarSizeExtraSpacePx = 0;
340         hotseatBarTopPaddingPx =
341                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
342         hotseatBarBottomPaddingPx = (isTallDevice ? 0
343                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding))
344                 + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
345         hotseatBarSidePaddingEndPx =
346                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
347         // Add a bit of space between nav bar and hotseat in vertical bar layout.
348         hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
349         hotseatExtraVerticalSize =
350                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size);
351         updateHotseatIconSize(
352                 pxFromDp(inv.iconSize[InvariantDeviceProfile.INDEX_DEFAULT], mMetrics, 1f));
353 
354         qsbBottomMarginOriginalPx = isScalableGrid
355                 ? res.getDimensionPixelSize(R.dimen.scalable_grid_qsb_bottom_margin)
356                 : 0;
357 
358         overviewShowAsGrid = isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
359         overviewTaskMarginPx = overviewShowAsGrid
360                 ? res.getDimensionPixelSize(R.dimen.overview_task_margin_focused)
361                 : res.getDimensionPixelSize(R.dimen.overview_task_margin);
362         overviewTaskMarginGridPx = res.getDimensionPixelSize(R.dimen.overview_task_margin_grid);
363         overviewTaskIconSizePx = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
364         overviewTaskIconDrawableSizePx =
365                 res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size);
366         overviewTaskIconDrawableSizeGridPx =
367                 res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid);
368         overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx * 2;
369         if (overviewShowAsGrid) {
370             if (isLandscape) {
371                 overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
372                         R.dimen.overview_actions_top_margin_gesture_grid_landscape);
373                 overviewActionsBottomMarginGesturePx = res.getDimensionPixelSize(
374                         R.dimen.overview_actions_bottom_margin_gesture_grid_landscape);
375                 overviewPageSpacing = res.getDimensionPixelSize(
376                         R.dimen.overview_page_spacing_grid_landscape);
377             } else {
378                 overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
379                         R.dimen.overview_actions_top_margin_gesture_grid_portrait);
380                 overviewActionsBottomMarginGesturePx = res.getDimensionPixelSize(
381                         R.dimen.overview_actions_bottom_margin_gesture_grid_portrait);
382                 overviewPageSpacing = res.getDimensionPixelSize(
383                         R.dimen.overview_page_spacing_grid_portrait);
384             }
385             overviewActionsButtonSpacing = res.getDimensionPixelSize(
386                     R.dimen.overview_actions_button_spacing_grid);
387         } else {
388             overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
389                     R.dimen.overview_actions_margin_gesture);
390             overviewActionsBottomMarginGesturePx = overviewActionsTopMarginGesturePx;
391             overviewActionsButtonSpacing = res.getDimensionPixelSize(
392                     R.dimen.overview_actions_button_spacing);
393             overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
394         }
395         overviewActionsMarginThreeButtonPx = res.getDimensionPixelSize(
396                 R.dimen.overview_actions_margin_three_button);
397         // Grid task's top margin is only overviewTaskIconSizePx + overviewTaskMarginGridPx, but
398         // overviewTaskThumbnailTopMarginPx is applied to all TaskThumbnailView, so exclude the
399         // extra  margin when calculating row spacing.
400         int extraTopMargin = overviewTaskThumbnailTopMarginPx - overviewTaskIconSizePx
401                 - overviewTaskMarginGridPx;
402         overviewRowSpacing = res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing)
403                 - extraTopMargin;
404         overviewGridSideMargin = isLandscape
405                 ? res.getDimensionPixelSize(R.dimen.overview_grid_side_margin_landscape)
406                 : res.getDimensionPixelSize(R.dimen.overview_grid_side_margin_portrait);
407 
408         // Calculate all of the remaining variables.
409         extraSpace = updateAvailableDimensions(res);
410 
411         // Now that we have all of the variables calculated, we can tune certain sizes.
412         if (isScalableGrid && inv.devicePaddings != null) {
413             // Paddings were created assuming no scaling, so we first unscale the extra space.
414             int unscaledExtraSpace = (int) (extraSpace / cellScaleToFit);
415             DevicePadding padding = inv.devicePaddings.getDevicePadding(unscaledExtraSpace);
416 
417             int paddingWorkspaceTop = padding.getWorkspaceTopPadding(unscaledExtraSpace);
418             int paddingWorkspaceBottom = padding.getWorkspaceBottomPadding(unscaledExtraSpace);
419             int paddingHotseatBottom = padding.getHotseatBottomPadding(unscaledExtraSpace);
420 
421             workspaceTopPadding = Math.round(paddingWorkspaceTop * cellScaleToFit);
422             workspaceBottomPadding = Math.round(paddingWorkspaceBottom * cellScaleToFit);
423             extraHotseatBottomPadding = Math.round(paddingHotseatBottom * cellScaleToFit);
424 
425             hotseatBarSizePx += extraHotseatBottomPadding;
426 
427             qsbBottomMarginPx = Math.round(qsbBottomMarginOriginalPx * cellScaleToFit);
428         } else if (!isVerticalBarLayout() && isPhone && isTallDevice) {
429             // We increase the hotseat size when there is extra space.
430 
431             if (Float.compare(aspectRatio, TALLER_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0
432                     && extraSpace >= Utilities.dpToPx(TALL_DEVICE_EXTRA_SPACE_THRESHOLD_DP)) {
433                 // For taller devices, we will take a piece of the extra space from each row,
434                 // and add it to the space above and below the hotseat.
435 
436                 // For devices with more extra space, we take a larger piece from each cell.
437                 int piece = extraSpace < Utilities.dpToPx(TALL_DEVICE_MORE_EXTRA_SPACE_THRESHOLD_DP)
438                         ? 7 : 5;
439 
440                 int extraSpace = ((getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2)
441                         * inv.numRows) / piece;
442 
443                 workspaceTopPadding = extraSpace / 8;
444                 int halfLeftOver = (extraSpace - workspaceTopPadding) / 2;
445                 hotseatBarTopPaddingPx += halfLeftOver;
446                 hotseatBarSizeExtraSpacePx = halfLeftOver;
447             } else {
448                 // ie. For a display with a large aspect ratio, we can keep the icons on the
449                 // workspace in portrait mode closer together by adding more height to the hotseat.
450                 // Note: This calculation was created after noticing a pattern in the design spec.
451                 hotseatBarSizeExtraSpacePx = getCellSize().y - iconSizePx
452                         - iconDrawablePaddingPx * 2 - workspacePageIndicatorHeight;
453             }
454 
455             updateHotseatIconSize(iconSizePx);
456 
457             // Recalculate the available dimensions using the new hotseat size.
458             updateAvailableDimensions(res);
459         }
460         updateWorkspacePadding();
461 
462         flingToDeleteThresholdVelocity = res.getDimensionPixelSize(
463                 R.dimen.drag_flingToDeleteMinVelocity);
464 
465         // This is done last, after iconSizePx is calculated above.
466         Path dotPath = GraphicsUtils.getShapePath(DEFAULT_DOT_SIZE);
467         mDotRendererWorkSpace = new DotRenderer(iconSizePx, dotPath, DEFAULT_DOT_SIZE);
468         mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace :
469                 new DotRenderer(allAppsIconSizePx, dotPath, DEFAULT_DOT_SIZE);
470     }
471 
472     private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) {
473         if (isVerticalBarLayout()) {
474             return 0;
475         }
476 
477         return isScalableGrid
478                 ? pxFromDp(idp.horizontalMargin[mTypeIndex], mMetrics)
479                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_left_right_margin);
480     }
481 
482     private void updateHotseatIconSize(int hotseatIconSizePx) {
483         // Ensure there is enough space for folder icons, which have a slightly larger radius.
484         hotseatCellHeightPx = (int) Math.ceil(hotseatIconSizePx * ICON_OVERLAP_FACTOR);
485         if (isVerticalBarLayout()) {
486             hotseatBarSizePx = hotseatIconSizePx + hotseatBarSidePaddingStartPx
487                     + hotseatBarSidePaddingEndPx;
488         } else {
489             hotseatBarSizePx = hotseatIconSizePx + hotseatBarTopPaddingPx
490                     + hotseatBarBottomPaddingPx + (isScalableGrid ? 0 : hotseatExtraVerticalSize)
491                     + hotseatBarSizeExtraSpacePx;
492         }
493     }
494 
495     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
496         if (!isScalableGrid) {
497             return new Point(0, 0);
498         }
499 
500         int horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics);
501         int verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics);
502 
503         return new Point(horizontalSpacePx, verticalSpacePx);
504     }
505 
506     private Point getCellLayoutBorderSpaceScaled(InvariantDeviceProfile idp, float scale) {
507         Point original = getCellLayoutBorderSpace(idp);
508         return new Point((int) (original.x * scale), (int) (original.y * scale));
509     }
510 
511     public Info getDisplayInfo() {
512         return mInfo;
513     }
514 
515     /**
516      * We inset the widget padding added by the system and instead rely on the border spacing
517      * between cells to create reliable consistency between widgets
518      */
519     public boolean shouldInsetWidgets() {
520         Rect widgetPadding = inv.defaultWidgetPadding;
521 
522         // Check all sides to ensure that the widget won't overlap into another cell, or into
523         // status bar.
524         return workspaceTopPadding > widgetPadding.top
525                 && cellLayoutBorderSpacePx.x > widgetPadding.left
526                 && cellLayoutBorderSpacePx.y > widgetPadding.top
527                 && cellLayoutBorderSpacePx.x > widgetPadding.right
528                 && cellLayoutBorderSpacePx.y > widgetPadding.bottom;
529     }
530 
toBuilder(Context context)531     public Builder toBuilder(Context context) {
532         WindowBounds bounds =
533                 new WindowBounds(widthPx, heightPx, availableWidthPx, availableHeightPx);
534         bounds.bounds.offsetTo(windowX, windowY);
535         return new Builder(context, inv, mInfo)
536                 .setWindowBounds(bounds)
537                 .setUseTwoPanels(isTwoPanels)
538                 .setMultiWindowMode(isMultiWindowMode);
539     }
540 
copy(Context context)541     public DeviceProfile copy(Context context) {
542         return toBuilder(context).build();
543     }
544 
545     /**
546      * TODO: Move this to the builder as part of setMultiWindowMode
547      */
getMultiWindowProfile(Context context, WindowBounds windowBounds)548     public DeviceProfile getMultiWindowProfile(Context context, WindowBounds windowBounds) {
549         DeviceProfile profile = toBuilder(context)
550                 .setWindowBounds(windowBounds)
551                 .setMultiWindowMode(true)
552                 .build();
553 
554         profile.hideWorkspaceLabelsIfNotEnoughSpace();
555 
556         // We use these scales to measure and layout the widgets using their full invariant profile
557         // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
558         float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
559         float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
560         profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
561         profile.updateWorkspacePadding();
562 
563         return profile;
564     }
565 
566     /**
567      * Checks if there is enough space for labels on the workspace.
568      * If there is not, labels on the Workspace are hidden.
569      * It is important to call this method after the All Apps variables have been set.
570      */
hideWorkspaceLabelsIfNotEnoughSpace()571     private void hideWorkspaceLabelsIfNotEnoughSpace() {
572         float iconTextHeight = Utilities.calculateTextHeight(iconTextSizePx);
573         float workspaceCellPaddingY = getCellSize().y - iconSizePx - iconDrawablePaddingPx
574                 - iconTextHeight;
575 
576         // We want enough space so that the text is closer to its corresponding icon.
577         if (workspaceCellPaddingY < iconTextHeight) {
578             iconTextSizePx = 0;
579             iconDrawablePaddingPx = 0;
580             cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR);
581             autoResizeAllAppsCells();
582         }
583     }
584 
585     /**
586      * Re-computes the all-apps cell size to be independent of workspace
587      */
autoResizeAllAppsCells()588     public void autoResizeAllAppsCells() {
589         int textHeight = Utilities.calculateTextHeight(allAppsIconTextSizePx);
590         int topBottomPadding = textHeight;
591         allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
592                 + textHeight + (topBottomPadding * 2);
593     }
594 
updateAllAppsWidth()595     private void updateAllAppsWidth() {
596         if (isTwoPanels) {
597             int usedWidth = (allAppsCellWidthPx * numShownAllAppsColumns)
598                     + (allAppsCellSpacePx.x * (numShownAllAppsColumns + 1));
599             allAppsLeftRightPadding = Math.max(1, (availableWidthPx - usedWidth) / 2);
600         } else {
601             allAppsLeftRightPadding =
602                     desiredWorkspaceHorizontalMarginPx + cellLayoutPaddingLeftRightPx;
603         }
604     }
605 
606     /**
607      * Returns the amount of extra (or unused) vertical space.
608      */
updateAvailableDimensions(Resources res)609     private int updateAvailableDimensions(Resources res) {
610         updateIconSize(1f, res);
611 
612         Point workspacePadding = getTotalWorkspacePadding();
613 
614         // Check to see if the icons fit within the available height.
615         float usedHeight = getCellLayoutHeight();
616         final int maxHeight = availableHeightPx - workspacePadding.y;
617         float extraHeight = Math.max(0, maxHeight - usedHeight);
618         float scaleY = maxHeight / usedHeight;
619         boolean shouldScale = scaleY < 1f;
620 
621         float scaleX = 1f;
622         if (isScalableGrid) {
623             // We scale to fit the cellWidth and cellHeight in the available space.
624             // The benefit of scalable grids is that we can get consistent aspect ratios between
625             // devices.
626             int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
627             float usedWidth = (cellWidthPx * numColumns)
628                     + (cellLayoutBorderSpacePx.x * (numColumns - 1))
629                     + (desiredWorkspaceHorizontalMarginPx * 2);
630             // We do not subtract padding here, as we also scale the workspace padding if needed.
631             scaleX = availableWidthPx / usedWidth;
632             shouldScale = true;
633         }
634 
635         if (shouldScale) {
636             float scale = Math.min(scaleX, scaleY);
637             updateIconSize(scale, res);
638             extraHeight = Math.max(0, maxHeight - getCellLayoutHeight());
639         }
640 
641         updateAvailableFolderCellDimensions(res);
642         return Math.round(extraHeight);
643     }
644 
645     private int getCellLayoutHeight() {
646         return (cellHeightPx * inv.numRows) + (cellLayoutBorderSpacePx.y * (inv.numRows - 1));
647     }
648 
649     /**
650      * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx,
651      * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
652      * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
653      */
654     public void updateIconSize(float scale, Resources res) {
655         // Icon scale should never exceed 1, otherwise pixellation may occur.
656         iconScale = Math.min(1f, scale);
657         cellScaleToFit = scale;
658 
659         // Workspace
660         final boolean isVerticalLayout = isVerticalBarLayout();
661         float invIconSizeDp = inv.iconSize[mTypeIndex];
662         float invIconTextSizeSp = inv.iconTextSize[mTypeIndex];
663 
664         iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, iconScale));
665         iconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * iconScale);
666         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * iconScale);
667 
668         cellLayoutBorderSpacePx = getCellLayoutBorderSpaceScaled(inv, scale);
669 
670         if (isScalableGrid) {
671             cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale);
672             cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale);
673             int cellContentHeight = iconSizePx + iconDrawablePaddingPx
674                     + Utilities.calculateTextHeight(iconTextSizePx);
675             cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
676             desiredWorkspaceHorizontalMarginPx =
677                     (int) (desiredWorkspaceHorizontalMarginOriginalPx * scale);
678         } else {
679             cellWidthPx = iconSizePx + iconDrawablePaddingPx;
680             cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR)
681                     + iconDrawablePaddingPx
682                     + Utilities.calculateTextHeight(iconTextSizePx);
683             int cellPaddingY = (getCellSize().y - cellHeightPx) / 2;
684             if (iconDrawablePaddingPx > cellPaddingY && !isVerticalLayout
685                     && !isMultiWindowMode) {
686                 // Ensures that the label is closer to its corresponding icon. This is not an issue
687                 // with vertical bar layout or multi-window mode since the issue is handled
688                 // separately with their calls to {@link #adjustToHideWorkspaceLabels}.
689                 cellHeightPx -= (iconDrawablePaddingPx - cellPaddingY);
690                 iconDrawablePaddingPx = cellPaddingY;
691             }
692         }
693 
694         // All apps
695         if (numShownAllAppsColumns != inv.numColumns) {
696             allAppsIconSizePx =
697                     pxFromDp(inv.iconSize[InvariantDeviceProfile.INDEX_ALL_APPS], mMetrics);
698             allAppsIconTextSizePx =
699                     pxFromSp(inv.iconTextSize[InvariantDeviceProfile.INDEX_ALL_APPS], mMetrics);
700             allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
701             autoResizeAllAppsCells();
702         } else {
703             allAppsIconSizePx = iconSizePx;
704             allAppsIconTextSizePx = iconTextSizePx;
705             allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
706             allAppsCellHeightPx = getCellSize().y;
707         }
708         allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
709         updateAllAppsWidth();
710 
711         if (isVerticalLayout) {
712             hideWorkspaceLabelsIfNotEnoughSpace();
713         }
714 
715         // Hotseat
716         updateHotseatIconSize(iconSizePx);
717 
718         if (!isVerticalLayout) {
719             int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx
720                     - workspacePageIndicatorHeight - edgeMarginPx;
721             float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
722             workspaceSpringLoadShrinkFactor = Math.min(
723                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f,
724                     1 - (minRequiredHeight / expectedWorkspaceHeight));
725         } else {
726             workspaceSpringLoadShrinkFactor =
727                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
728         }
729 
730         // Folder icon
731         folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
732         folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
733     }
734 
updateAvailableFolderCellDimensions(Resources res)735     private void updateAvailableFolderCellDimensions(Resources res) {
736         updateFolderCellSize(1f, res);
737 
738         final int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_height);
739 
740         // Don't let the folder get too close to the edges of the screen.
741         int folderMargin = edgeMarginPx * 2;
742         Point totalWorkspacePadding = getTotalWorkspacePadding();
743 
744         // Check if the icons fit within the available height.
745         float contentUsedHeight = folderCellHeightPx * inv.numFolderRows
746                 + ((inv.numFolderRows - 1) * folderCellLayoutBorderSpacePx.y);
747         int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize
748                 - folderMargin - folderContentPaddingTop;
749         float scaleY = contentMaxHeight / contentUsedHeight;
750 
751         // Check if the icons fit within the available width.
752         float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns
753                 + ((inv.numFolderColumns - 1) * folderCellLayoutBorderSpacePx.x);
754         int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin
755                 - folderContentPaddingLeftRight * 2;
756         float scaleX = contentMaxWidth / contentUsedWidth;
757 
758         float scale = Math.min(scaleX, scaleY);
759         if (scale < 1f) {
760             updateFolderCellSize(scale, res);
761         }
762     }
763 
updateFolderCellSize(float scale, Resources res)764     private void updateFolderCellSize(float scale, Resources res) {
765         float invIconSizeDp = isVerticalBarLayout()
766                 ? inv.iconSize[InvariantDeviceProfile.INDEX_LANDSCAPE]
767                 : inv.iconSize[InvariantDeviceProfile.INDEX_DEFAULT];
768         folderChildIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale));
769         folderChildTextSizePx =
770                 pxFromSp(inv.iconTextSize[InvariantDeviceProfile.INDEX_DEFAULT], mMetrics, scale);
771         folderLabelTextSizePx = (int) (folderChildTextSizePx * folderLabelTextScale);
772 
773         int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
774 
775         if (isScalableGrid) {
776             int minWidth = folderChildIconSizePx + iconDrawablePaddingPx * 2;
777             int minHeight = folderChildIconSizePx + iconDrawablePaddingPx * 2 + textHeight;
778 
779             folderCellWidthPx = (int) Math.max(minWidth, cellWidthPx * scale);
780             folderCellHeightPx = (int) Math.max(minHeight, cellHeightPx * scale);
781 
782             int scaledSpace = (int) (folderCellLayoutBorderSpaceOriginalPx * scale);
783             folderCellLayoutBorderSpacePx = new Point(scaledSpace, scaledSpace);
784             folderContentPaddingLeftRight = scaledSpace;
785             folderContentPaddingTop = scaledSpace;
786         } else {
787             int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding)
788                     * scale);
789             int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding)
790                     * scale);
791 
792             folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
793             folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
794         }
795 
796         folderChildDrawablePaddingPx = Math.max(0,
797                 (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
798     }
799 
updateInsets(Rect insets)800     public void updateInsets(Rect insets) {
801         mInsets.set(insets);
802         updateWorkspacePadding();
803     }
804 
805     /**
806      * The current device insets. This is generally same as the insets being dispatched to
807      * {@link Insettable} elements, but can differ if the element is using a different profile.
808      */
getInsets()809     public Rect getInsets() {
810         return mInsets;
811     }
812 
getCellSize()813     public Point getCellSize() {
814         return getCellSize(null);
815     }
816 
getCellSize(Point result)817     public Point getCellSize(Point result) {
818         if (result == null) {
819             result = new Point();
820         }
821 
822         // Since we are only concerned with the overall padding, layout direction does
823         // not matter.
824         Point padding = getTotalWorkspacePadding();
825 
826         int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
827         int screenWidthPx = getWorkspaceWidth(padding);
828         result.x = calculateCellWidth(screenWidthPx, cellLayoutBorderSpacePx.x, numColumns);
829         result.y = calculateCellHeight(availableHeightPx - padding.y
830                 - cellLayoutBottomPaddingPx, cellLayoutBorderSpacePx.y, inv.numRows);
831         return result;
832     }
833 
getWorkspaceWidth()834     public int getWorkspaceWidth() {
835         return getWorkspaceWidth(getTotalWorkspacePadding());
836     }
837 
getWorkspaceWidth(Point workspacePadding)838     public int getWorkspaceWidth(Point workspacePadding) {
839         int cellLayoutTotalPadding =
840                 isTwoPanels ? 4 * cellLayoutPaddingLeftRightPx : 2 * cellLayoutPaddingLeftRightPx;
841         return availableWidthPx - workspacePadding.x - cellLayoutTotalPadding;
842     }
843 
getTotalWorkspacePadding()844     public Point getTotalWorkspacePadding() {
845         updateWorkspacePadding();
846         return new Point(workspacePadding.left + workspacePadding.right,
847                 workspacePadding.top + workspacePadding.bottom);
848     }
849 
850     /**
851      * Updates {@link #workspacePadding} as a result of any internal value change to reflect the
852      * new workspace padding
853      */
updateWorkspacePadding()854     private void updateWorkspacePadding() {
855         Rect padding = workspacePadding;
856         if (isVerticalBarLayout()) {
857             padding.top = 0;
858             padding.bottom = edgeMarginPx;
859             if (isSeascape()) {
860                 padding.left = hotseatBarSizePx;
861                 padding.right = hotseatBarSidePaddingStartPx;
862             } else {
863                 padding.left = hotseatBarSidePaddingStartPx;
864                 padding.right = hotseatBarSizePx;
865             }
866         } else {
867             // Pad the bottom of the workspace with search/hotseat bar sizes
868             int hotseatTop = hotseatBarSizePx;
869             int paddingBottom = hotseatTop + workspacePageIndicatorHeight
870                     + workspaceBottomPadding - mWorkspacePageIndicatorOverlapWorkspace;
871 
872             padding.set(desiredWorkspaceHorizontalMarginPx,
873                     workspaceTopPadding + (isScalableGrid ? 0 : edgeMarginPx),
874                     desiredWorkspaceHorizontalMarginPx,
875                     paddingBottom);
876         }
877     }
878 
879     /**
880      * Returns the padding for hotseat view
881      */
getHotseatLayoutPadding(Context context)882     public Rect getHotseatLayoutPadding(Context context) {
883         if (isVerticalBarLayout()) {
884             if (isSeascape()) {
885                 mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx,
886                         mInsets.top, hotseatBarSidePaddingEndPx, mInsets.bottom);
887             } else {
888                 mHotseatPadding.set(hotseatBarSidePaddingEndPx, mInsets.top,
889                         mInsets.right + hotseatBarSidePaddingStartPx, mInsets.bottom);
890             }
891         } else if (isTaskbarPresent) {
892             int hotseatHeight = workspacePadding.bottom;
893             int taskbarOffset = getTaskbarOffsetY();
894             int hotseatTopDiff = hotseatHeight - taskbarOffset;
895 
896             int endOffset = ApiWrapper.getHotseatEndOffset(context);
897             int requiredWidth = iconSizePx * numShownHotseatIcons;
898 
899             Resources res = context.getResources();
900             float taskbarIconSize = res.getDimension(R.dimen.taskbar_icon_size);
901             float taskbarIconSpacing = 2 * res.getDimension(R.dimen.taskbar_icon_spacing);
902             int maxSize = (int) (requiredWidth
903                     * (taskbarIconSize + taskbarIconSpacing) / taskbarIconSize);
904             int hotseatSize = Math.min(maxSize, availableWidthPx - endOffset);
905             int sideSpacing = (availableWidthPx - hotseatSize) / 2;
906             mHotseatPadding.set(sideSpacing, hotseatTopDiff, sideSpacing, taskbarOffset);
907 
908             if (endOffset > sideSpacing) {
909                 int diff = Utilities.isRtl(context.getResources())
910                         ? sideSpacing - endOffset
911                         : endOffset - sideSpacing;
912                 mHotseatPadding.left -= diff;
913                 mHotseatPadding.right += diff;
914             }
915         } else {
916             // We want the edges of the hotseat to line up with the edges of the workspace, but the
917             // icons in the hotseat are a different size, and so don't line up perfectly. To account
918             // for this, we pad the left and right of the hotseat with half of the difference of a
919             // workspace cell vs a hotseat cell.
920             float workspaceCellWidth = (float) widthPx / inv.numColumns;
921             float hotseatCellWidth = (float) widthPx / numShownHotseatIcons;
922             int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
923             mHotseatPadding.set(
924                     hotseatAdjustment + workspacePadding.left + cellLayoutPaddingLeftRightPx
925                             + mInsets.left,
926                     hotseatBarTopPaddingPx,
927                     hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx
928                             + mInsets.right,
929                     hotseatBarSizePx - hotseatCellHeightPx - hotseatBarTopPaddingPx
930                             + cellLayoutBottomPaddingPx + mInsets.bottom);
931         }
932         return mHotseatPadding;
933     }
934 
935     /**
936      * Returns the number of pixels the QSB is translated from the bottom of the screen.
937      */
getQsbOffsetY()938     public int getQsbOffsetY() {
939         int freeSpace = isTaskbarPresent
940                 ? workspacePadding.bottom
941                 : hotseatBarSizePx - hotseatCellHeightPx - hotseatQsbHeight;
942 
943         if (isScalableGrid && qsbBottomMarginPx > mInsets.bottom) {
944             // Note that taskbarSize = 0 unless isTaskbarPresent.
945             return Math.min(qsbBottomMarginPx + taskbarSize, freeSpace);
946         } else {
947             return (int) (freeSpace * QSB_CENTER_FACTOR)
948                     + (isTaskbarPresent ? taskbarSize : mInsets.bottom);
949         }
950     }
951 
952     /**
953      * Returns the number of pixels the taskbar is translated from the bottom of the screen.
954      */
getTaskbarOffsetY()955     public int getTaskbarOffsetY() {
956         return (getQsbOffsetY() - taskbarSize) / 2;
957     }
958 
959     /**
960      * @return the bounds for which the open folders should be contained within
961      */
getAbsoluteOpenFolderBounds()962     public Rect getAbsoluteOpenFolderBounds() {
963         if (isVerticalBarLayout()) {
964             // Folders should only appear right of the drop target bar and left of the hotseat
965             return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx,
966                     mInsets.top,
967                     mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx,
968                     mInsets.top + availableHeightPx);
969         } else {
970             // Folders should only appear below the drop target bar and above the hotseat
971             int hotseatTop = isTaskbarPresent ? taskbarSize : hotseatBarSizePx;
972             return new Rect(mInsets.left + edgeMarginPx,
973                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
974                     mInsets.left + availableWidthPx - edgeMarginPx,
975                     mInsets.top + availableHeightPx - hotseatTop
976                             - workspacePageIndicatorHeight - edgeMarginPx);
977         }
978     }
979 
calculateCellWidth(int width, int borderSpacing, int countX)980     public static int calculateCellWidth(int width, int borderSpacing, int countX) {
981         return (width - ((countX - 1) * borderSpacing)) / countX;
982     }
983 
calculateCellHeight(int height, int borderSpacing, int countY)984     public static int calculateCellHeight(int height, int borderSpacing, int countY) {
985         return (height - ((countY - 1) * borderSpacing)) / countY;
986     }
987 
988     /**
989      * When {@code true}, the device is in landscape mode and the hotseat is on the right column.
990      * When {@code false}, either device is in portrait mode or the device is in landscape mode and
991      * the hotseat is on the bottom row.
992      */
isVerticalBarLayout()993     public boolean isVerticalBarLayout() {
994         return isLandscape && transposeLayoutWithOrientation;
995     }
996 
997     /**
998      * Updates orientation information and returns true if it has changed from the previous value.
999      */
updateIsSeascape(Context context)1000     public boolean updateIsSeascape(Context context) {
1001         if (isVerticalBarLayout()) {
1002             boolean isSeascape = DisplayController.INSTANCE.get(context)
1003                     .getInfo().rotation == Surface.ROTATION_270;
1004             if (mIsSeascape != isSeascape) {
1005                 mIsSeascape = isSeascape;
1006                 return true;
1007             }
1008         }
1009         return false;
1010     }
1011 
isSeascape()1012     public boolean isSeascape() {
1013         return isVerticalBarLayout() && mIsSeascape;
1014     }
1015 
shouldFadeAdjacentWorkspaceScreens()1016     public boolean shouldFadeAdjacentWorkspaceScreens() {
1017         return isVerticalBarLayout();
1018     }
1019 
getCellContentHeight(@ontainerType int containerType)1020     public int getCellContentHeight(@ContainerType int containerType) {
1021         switch (containerType) {
1022             case CellLayout.WORKSPACE:
1023                 return cellHeightPx;
1024             case CellLayout.FOLDER:
1025                 return folderCellHeightPx;
1026             case CellLayout.HOTSEAT:
1027                 // The hotseat is the only container where the cell height is going to be
1028                 // different from the content within that cell.
1029                 return iconSizePx;
1030             default:
1031                 // ??
1032                 return 0;
1033         }
1034     }
1035 
pxToDpStr(String name, float value)1036     private String pxToDpStr(String name, float value) {
1037         return "\t" + name + ": " + value + "px (" + dpiFromPx(value, mMetrics.densityDpi) + "dp)";
1038     }
1039 
dump(String prefix, PrintWriter writer)1040     public void dump(String prefix, PrintWriter writer) {
1041         writer.println(prefix + "DeviceProfile:");
1042         writer.println(prefix + "\t1 dp = " + mMetrics.density + " px");
1043 
1044         writer.println(prefix + "\tisTablet:" + isTablet);
1045         writer.println(prefix + "\tisPhone:" + isPhone);
1046         writer.println(prefix + "\ttransposeLayoutWithOrientation:"
1047                 + transposeLayoutWithOrientation);
1048 
1049         writer.println(prefix + "\tisLandscape:" + isLandscape);
1050         writer.println(prefix + "\tisMultiWindowMode:" + isMultiWindowMode);
1051         writer.println(prefix + "\tisTwoPanels:" + isTwoPanels);
1052 
1053         writer.println(prefix + pxToDpStr("windowX", windowX));
1054         writer.println(prefix + pxToDpStr("windowY", windowY));
1055         writer.println(prefix + pxToDpStr("widthPx", widthPx));
1056         writer.println(prefix + pxToDpStr("heightPx", heightPx));
1057 
1058         writer.println(prefix + pxToDpStr("availableWidthPx", availableWidthPx));
1059         writer.println(prefix + pxToDpStr("availableHeightPx", availableHeightPx));
1060 
1061         writer.println(prefix + "\taspectRatio:" + aspectRatio);
1062 
1063         writer.println(prefix + "\tisScalableGrid:" + isScalableGrid);
1064 
1065         writer.println(prefix + "\tinv.numColumns: " + inv.numColumns);
1066         writer.println(prefix + "\tinv.numRows: " + inv.numRows);
1067 
1068         writer.println(prefix + "\tminCellSize: " + inv.minCellSize[mTypeIndex] + "dp");
1069 
1070         writer.println(prefix + pxToDpStr("cellWidthPx", cellWidthPx));
1071         writer.println(prefix + pxToDpStr("cellHeightPx", cellHeightPx));
1072 
1073         writer.println(prefix + pxToDpStr("getCellSize().x", getCellSize().x));
1074         writer.println(prefix + pxToDpStr("getCellSize().y", getCellSize().y));
1075 
1076         writer.println(prefix + pxToDpStr("cellLayoutBorderSpacePx Horizontal",
1077                 cellLayoutBorderSpacePx.x));
1078         writer.println(prefix + pxToDpStr("cellLayoutBorderSpacePx Vertical",
1079                 cellLayoutBorderSpacePx.y));
1080 
1081         writer.println(prefix + pxToDpStr("iconSizePx", iconSizePx));
1082         writer.println(prefix + pxToDpStr("iconTextSizePx", iconTextSizePx));
1083         writer.println(prefix + pxToDpStr("iconDrawablePaddingPx", iconDrawablePaddingPx));
1084 
1085         writer.println(prefix + pxToDpStr("folderCellWidthPx", folderCellWidthPx));
1086         writer.println(prefix + pxToDpStr("folderCellHeightPx", folderCellHeightPx));
1087         writer.println(prefix + pxToDpStr("folderChildIconSizePx", folderChildIconSizePx));
1088         writer.println(prefix + pxToDpStr("folderChildTextSizePx", folderChildTextSizePx));
1089         writer.println(prefix + pxToDpStr("folderChildDrawablePaddingPx",
1090                 folderChildDrawablePaddingPx));
1091         writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpaceOriginalPx",
1092                 folderCellLayoutBorderSpaceOriginalPx));
1093         writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx Horizontal",
1094                 folderCellLayoutBorderSpacePx.x));
1095         writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx Vertical",
1096                 folderCellLayoutBorderSpacePx.y));
1097 
1098         writer.println(prefix + pxToDpStr("allAppsIconSizePx", allAppsIconSizePx));
1099         writer.println(prefix + pxToDpStr("allAppsIconTextSizePx", allAppsIconTextSizePx));
1100         writer.println(prefix + pxToDpStr("allAppsIconDrawablePaddingPx",
1101                 allAppsIconDrawablePaddingPx));
1102         writer.println(prefix + pxToDpStr("allAppsCellHeightPx", allAppsCellHeightPx));
1103         writer.println(prefix + "\tnumShownAllAppsColumns: " + numShownAllAppsColumns);
1104 
1105         writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx));
1106         writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx));
1107         writer.println(prefix + pxToDpStr("hotseatBarTopPaddingPx", hotseatBarTopPaddingPx));
1108         writer.println(prefix + pxToDpStr("hotseatBarBottomPaddingPx", hotseatBarBottomPaddingPx));
1109         writer.println(prefix + pxToDpStr("hotseatBarSidePaddingStartPx",
1110                 hotseatBarSidePaddingStartPx));
1111         writer.println(prefix + pxToDpStr("hotseatBarSidePaddingEndPx",
1112                 hotseatBarSidePaddingEndPx));
1113         writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons);
1114 
1115         writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent);
1116         writer.println(prefix + "\tisTaskbarPresentInApps:" + isTaskbarPresentInApps);
1117         writer.println(prefix + pxToDpStr("taskbarSize", taskbarSize));
1118 
1119         writer.println(prefix + pxToDpStr("desiredWorkspaceHorizontalMarginPx",
1120                 desiredWorkspaceHorizontalMarginPx));
1121         writer.println(prefix + pxToDpStr("workspacePadding.left", workspacePadding.left));
1122         writer.println(prefix + pxToDpStr("workspacePadding.top", workspacePadding.top));
1123         writer.println(prefix + pxToDpStr("workspacePadding.right", workspacePadding.right));
1124         writer.println(prefix + pxToDpStr("workspacePadding.bottom", workspacePadding.bottom));
1125 
1126         writer.println(prefix + pxToDpStr("iconScale", iconScale));
1127         writer.println(prefix + pxToDpStr("cellScaleToFit ", cellScaleToFit));
1128         writer.println(prefix + pxToDpStr("extraSpace", extraSpace));
1129         writer.println(prefix + pxToDpStr("unscaled extraSpace", extraSpace / iconScale));
1130 
1131         if (inv.devicePaddings != null) {
1132             int unscaledExtraSpace = (int) (extraSpace / iconScale);
1133             writer.println(prefix + pxToDpStr("maxEmptySpace",
1134                     inv.devicePaddings.getDevicePadding(unscaledExtraSpace).getMaxEmptySpacePx()));
1135         }
1136         writer.println(prefix + pxToDpStr("workspaceTopPadding", workspaceTopPadding));
1137         writer.println(prefix + pxToDpStr("workspaceBottomPadding", workspaceBottomPadding));
1138         writer.println(prefix + pxToDpStr("extraHotseatBottomPadding", extraHotseatBottomPadding));
1139     }
1140 
getContext(Context c, Info info, int orientation)1141     private static Context getContext(Context c, Info info, int orientation) {
1142         Configuration config = new Configuration(c.getResources().getConfiguration());
1143         config.orientation = orientation;
1144         config.densityDpi = info.densityDpi;
1145         return c.createConfigurationContext(config);
1146     }
1147 
1148     /**
1149      * Callback when a component changes the DeviceProfile associated with it, as a result of
1150      * configuration change
1151      */
1152     public interface OnDeviceProfileChangeListener {
1153 
1154         /**
1155          * Called when the device profile is reassigned. Note that for layout and measurements, it
1156          * is sufficient to listen for inset changes. Use this callback when you need to perform
1157          * a one time operation.
1158          */
1159         void onDeviceProfileChanged(DeviceProfile dp);
1160     }
1161 
1162     public static class Builder {
1163         private Context mContext;
1164         private InvariantDeviceProfile mInv;
1165         private Info mInfo;
1166 
1167         private WindowBounds mWindowBounds;
1168         private boolean mUseTwoPanels;
1169 
1170         private boolean mIsMultiWindowMode = false;
1171         private Boolean mTransposeLayoutWithOrientation;
1172 
Builder(Context context, InvariantDeviceProfile inv, Info info)1173         public Builder(Context context, InvariantDeviceProfile inv, Info info) {
1174             mContext = context;
1175             mInv = inv;
1176             mInfo = info;
1177         }
1178 
setMultiWindowMode(boolean isMultiWindowMode)1179         public Builder setMultiWindowMode(boolean isMultiWindowMode) {
1180             mIsMultiWindowMode = isMultiWindowMode;
1181             return this;
1182         }
1183 
setUseTwoPanels(boolean useTwoPanels)1184         public Builder setUseTwoPanels(boolean useTwoPanels) {
1185             mUseTwoPanels = useTwoPanels;
1186             return this;
1187         }
1188 
1189 
setWindowBounds(WindowBounds bounds)1190         public Builder setWindowBounds(WindowBounds bounds) {
1191             mWindowBounds = bounds;
1192             return this;
1193         }
1194 
setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation)1195         public Builder setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation) {
1196             mTransposeLayoutWithOrientation = transposeLayoutWithOrientation;
1197             return this;
1198         }
1199 
build()1200         public DeviceProfile build() {
1201             if (mWindowBounds == null) {
1202                 throw new IllegalArgumentException("Window bounds not set");
1203             }
1204             if (mTransposeLayoutWithOrientation == null) {
1205                 mTransposeLayoutWithOrientation = !mInfo.isTablet(mWindowBounds);
1206             }
1207             return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds,
1208                     mIsMultiWindowMode, mTransposeLayoutWithOrientation, mUseTwoPanels);
1209         }
1210     }
1211 
1212 }
1213