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