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 android.content.Context;
20 import android.content.res.Configuration;
21 import android.content.res.Resources;
22 import android.graphics.Point;
23 import android.graphics.PointF;
24 import android.graphics.Rect;
25 import android.util.DisplayMetrics;
26 import android.view.Surface;
27 
28 import androidx.annotation.Nullable;
29 
30 import com.android.launcher3.CellLayout.ContainerType;
31 import com.android.launcher3.graphics.IconShape;
32 import com.android.launcher3.icons.DotRenderer;
33 import com.android.launcher3.icons.IconNormalizer;
34 import com.android.launcher3.util.DefaultDisplay;
35 
36 public class DeviceProfile {
37 
38     public final InvariantDeviceProfile inv;
39     // IDP with no grid override values.
40     @Nullable private final InvariantDeviceProfile originalIdp;
41 
42     // Device properties
43     public final boolean isTablet;
44     public final boolean isLargeTablet;
45     public final boolean isPhone;
46     public final boolean transposeLayoutWithOrientation;
47 
48     // Device properties in current orientation
49     public final boolean isLandscape;
50     public final boolean isMultiWindowMode;
51 
52     public final int widthPx;
53     public final int heightPx;
54     public final int availableWidthPx;
55     public final int availableHeightPx;
56 
57     public final float aspectRatio;
58 
59     /**
60      * The maximum amount of left/right workspace padding as a percentage of the screen width.
61      * To be clear, this means that up to 7% of the screen width can be used as left padding, and
62      * 7% of the screen width can be used as right padding.
63      */
64     private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f;
65 
66     private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
67 
68     // To evenly space the icons, increase the left/right margins for tablets in portrait mode.
69     private static final int PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER = 4;
70 
71     // Workspace
72     public final int desiredWorkspaceLeftRightMarginPx;
73     public final int cellLayoutPaddingLeftRightPx;
74     public final int cellLayoutBottomPaddingPx;
75     public final int edgeMarginPx;
76     public float workspaceSpringLoadShrinkFactor;
77     public final int workspaceSpringLoadedBottomSpace;
78 
79     // Drag handle
80     public final int verticalDragHandleSizePx;
81     private final int verticalDragHandleOverlapWorkspace;
82 
83     // Workspace icons
84     public int iconSizePx;
85     public int iconTextSizePx;
86     public int iconDrawablePaddingPx;
87     public int iconDrawablePaddingOriginalPx;
88 
89     public int cellWidthPx;
90     public int cellHeightPx;
91     public int workspaceCellPaddingXPx;
92 
93     // Folder
94     public int folderIconSizePx;
95     public int folderIconOffsetYPx;
96 
97     // Folder cell
98     public int folderCellWidthPx;
99     public int folderCellHeightPx;
100 
101     // Folder child
102     public int folderChildIconSizePx;
103     public int folderChildTextSizePx;
104     public int folderChildDrawablePaddingPx;
105 
106     // Hotseat
107     public int hotseatCellHeightPx;
108     // In portrait: size = height, in landscape: size = width
109     public int hotseatBarSizePx;
110     public final int hotseatBarTopPaddingPx;
111     public int hotseatBarBottomPaddingPx;
112     // Start is the side next to the nav bar, end is the side next to the workspace
113     public final int hotseatBarSidePaddingStartPx;
114     public final int hotseatBarSidePaddingEndPx;
115 
116     // All apps
117     public int allAppsCellHeightPx;
118     public int allAppsCellWidthPx;
119     public int allAppsIconSizePx;
120     public int allAppsIconDrawablePaddingPx;
121     public float allAppsIconTextSizePx;
122 
123     // Widgets
124     public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
125 
126     // Drop Target
127     public int dropTargetBarSizePx;
128 
129     // Insets
130     private final Rect mInsets = new Rect();
131     public final Rect workspacePadding = new Rect();
132     private final Rect mHotseatPadding = new Rect();
133     // When true, nav bar is on the left side of the screen.
134     private boolean mIsSeascape;
135 
136     // Notification dots
137     public DotRenderer mDotRendererWorkSpace;
138     public DotRenderer mDotRendererAllApps;
139 
DeviceProfile(Context context, InvariantDeviceProfile inv, InvariantDeviceProfile originalIDP, Point minSize, Point maxSize, int width, int height, boolean isLandscape, boolean isMultiWindowMode)140     public DeviceProfile(Context context, InvariantDeviceProfile inv,
141             InvariantDeviceProfile originalIDP, Point minSize, Point maxSize,
142             int width, int height, boolean isLandscape, boolean isMultiWindowMode) {
143 
144         this.inv = inv;
145         this.originalIdp = inv;
146         this.isLandscape = isLandscape;
147         this.isMultiWindowMode = isMultiWindowMode;
148 
149         // Determine sizes.
150         widthPx = width;
151         heightPx = height;
152         if (isLandscape) {
153             availableWidthPx = maxSize.x;
154             availableHeightPx = minSize.y;
155         } else {
156             availableWidthPx = minSize.x;
157             availableHeightPx = maxSize.y;
158         }
159 
160         Resources res = context.getResources();
161         DisplayMetrics dm = res.getDisplayMetrics();
162 
163         // Constants from resources
164         isTablet = res.getBoolean(R.bool.is_tablet);
165         isLargeTablet = res.getBoolean(R.bool.is_large_tablet);
166         isPhone = !isTablet && !isLargeTablet;
167         aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
168         boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
169 
170         // Some more constants
171         transposeLayoutWithOrientation =
172                 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
173 
174         context = getContext(context, isVerticalBarLayout()
175                 ? Configuration.ORIENTATION_LANDSCAPE
176                 : Configuration.ORIENTATION_PORTRAIT);
177         res = context.getResources();
178 
179         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
180         desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
181 
182         int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
183                 ? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
184         int cellLayoutPadding = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
185         if (isLandscape) {
186             cellLayoutPaddingLeftRightPx = 0;
187             cellLayoutBottomPaddingPx = cellLayoutPadding;
188         } else {
189             cellLayoutPaddingLeftRightPx = cellLayoutPaddingLeftRightMultiplier * cellLayoutPadding;
190             cellLayoutBottomPaddingPx = 0;
191         }
192 
193         verticalDragHandleSizePx = res.getDimensionPixelSize(
194                 R.dimen.vertical_drag_handle_size);
195         verticalDragHandleOverlapWorkspace =
196                 res.getDimensionPixelSize(R.dimen.vertical_drag_handle_overlap_workspace);
197 
198         iconDrawablePaddingOriginalPx =
199                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
200         dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
201         workspaceSpringLoadedBottomSpace =
202                 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
203 
204         workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
205 
206         hotseatBarTopPaddingPx =
207                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
208         hotseatBarBottomPaddingPx = (isTallDevice ? 0
209                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding))
210                 + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
211         hotseatBarSidePaddingEndPx =
212                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
213         // Add a bit of space between nav bar and hotseat in vertical bar layout.
214         hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? verticalDragHandleSizePx : 0;
215         hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, dm) + (isVerticalBarLayout()
216                 ? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx)
217                 : (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
218                         + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx));
219 
220         // Calculate all of the remaining variables.
221         updateAvailableDimensions(dm, res);
222 
223         // Now that we have all of the variables calculated, we can tune certain sizes.
224         if (!isVerticalBarLayout() && isPhone && isTallDevice) {
225             // We increase the hotseat size when there is extra space.
226             // ie. For a display with a large aspect ratio, we can keep the icons on the workspace
227             // in portrait mode closer together by adding more height to the hotseat.
228             // Note: This calculation was created after noticing a pattern in the design spec.
229             int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
230                     - verticalDragHandleSizePx;
231             hotseatBarSizePx += extraSpace;
232             hotseatBarBottomPaddingPx += extraSpace;
233 
234             // Recalculate the available dimensions using the new hotseat size.
235             updateAvailableDimensions(dm, res);
236         }
237 
238         if (originalIDP != null) {
239             // Grid size change should not affect All Apps UI, so we use the original profile
240             // measurements here.
241             DeviceProfile originalProfile = isLandscape
242                     ? originalIDP.landscapeProfile
243                     : originalIDP.portraitProfile;
244             allAppsIconSizePx = originalProfile.iconSizePx;
245             allAppsIconTextSizePx = originalProfile.iconTextSizePx;
246             allAppsCellHeightPx = originalProfile.allAppsCellHeightPx;
247             allAppsIconDrawablePaddingPx = originalProfile.iconDrawablePaddingOriginalPx;
248             allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
249         }
250         updateWorkspacePadding();
251 
252         // This is done last, after iconSizePx is calculated above.
253         mDotRendererWorkSpace = new DotRenderer(iconSizePx, IconShape.getShapePath(),
254                 IconShape.DEFAULT_PATH_SIZE);
255         mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace :
256                 new DotRenderer(allAppsIconSizePx, IconShape.getShapePath(),
257                         IconShape.DEFAULT_PATH_SIZE);
258     }
259 
copy(Context context)260     public DeviceProfile copy(Context context) {
261         Point size = new Point(availableWidthPx, availableHeightPx);
262         return new DeviceProfile(context, inv, originalIdp, size, size, widthPx, heightPx,
263                 isLandscape, isMultiWindowMode);
264     }
265 
getMultiWindowProfile(Context context, Point mwSize)266     public DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
267         // We take the minimum sizes of this profile and it's multi-window variant to ensure that
268         // the system decor is always excluded.
269         mwSize.set(Math.min(availableWidthPx, mwSize.x), Math.min(availableHeightPx, mwSize.y));
270 
271         // In multi-window mode, we can have widthPx = availableWidthPx
272         // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
273         // widthPx and heightPx values where it's needed.
274         DeviceProfile profile = new DeviceProfile(context, inv, originalIdp, mwSize, mwSize,
275                 mwSize.x, mwSize.y, isLandscape, true);
276 
277         // If there isn't enough vertical cell padding with the labels displayed, hide the labels.
278         float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx
279                 - iconDrawablePaddingPx - profile.iconTextSizePx;
280         if (workspaceCellPaddingY < profile.iconDrawablePaddingPx * 2) {
281             profile.adjustToHideWorkspaceLabels();
282         }
283 
284         // We use these scales to measure and layout the widgets using their full invariant profile
285         // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
286         float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
287         float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
288         profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
289         profile.updateWorkspacePadding();
290 
291         return profile;
292     }
293 
294     /**
295      * Inverse of {@link #getMultiWindowProfile(Context, Point)}
296      * @return device profile corresponding to the current orientation in non multi-window mode.
297      */
getFullScreenProfile()298     public DeviceProfile getFullScreenProfile() {
299         return isLandscape ? inv.landscapeProfile : inv.portraitProfile;
300     }
301 
302     /**
303      * Adjusts the profile so that the labels on the Workspace are hidden.
304      * It is important to call this method after the All Apps variables have been set.
305      */
adjustToHideWorkspaceLabels()306     private void adjustToHideWorkspaceLabels() {
307         iconTextSizePx = 0;
308         iconDrawablePaddingPx = 0;
309         cellHeightPx = iconSizePx;
310 
311         // In normal cases, All Apps cell height should equal the Workspace cell height.
312         // Since we are removing labels from the Workspace, we need to manually compute the
313         // All Apps cell height.
314         int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1);
315         allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
316                 + Utilities.calculateTextHeight(allAppsIconTextSizePx)
317                 + topBottomPadding * 2;
318     }
319 
updateAvailableDimensions(DisplayMetrics dm, Resources res)320     private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
321         updateIconSize(1f, res, dm);
322 
323         // Check to see if the icons fit within the available height.  If not, then scale down.
324         float usedHeight = (cellHeightPx * inv.numRows);
325         int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
326         if (usedHeight > maxHeight) {
327             float scale = maxHeight / usedHeight;
328             updateIconSize(scale, res, dm);
329         }
330         updateAvailableFolderCellDimensions(dm, res);
331     }
332 
333     /**
334      * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx,
335      * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
336      * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
337      */
updateIconSize(float scale, Resources res, DisplayMetrics dm)338     private void updateIconSize(float scale, Resources res, DisplayMetrics dm) {
339         // Workspace
340         final boolean isVerticalLayout = isVerticalBarLayout();
341         float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
342         iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, dm) * scale));
343         iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
344         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
345 
346         cellHeightPx = iconSizePx + iconDrawablePaddingPx
347                 + Utilities.calculateTextHeight(iconTextSizePx);
348         int cellYPadding = (getCellSize().y - cellHeightPx) / 2;
349         if (iconDrawablePaddingPx > cellYPadding && !isVerticalLayout
350                 && !isMultiWindowMode) {
351             // Ensures that the label is closer to its corresponding icon. This is not an issue
352             // with vertical bar layout or multi-window mode since the issue is handled separately
353             // with their calls to {@link #adjustToHideWorkspaceLabels}.
354             cellHeightPx -= (iconDrawablePaddingPx - cellYPadding);
355             iconDrawablePaddingPx = cellYPadding;
356         }
357         cellWidthPx = iconSizePx + iconDrawablePaddingPx;
358 
359         allAppsIconSizePx = iconSizePx;
360         allAppsIconTextSizePx = iconTextSizePx;
361         allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
362         allAppsCellHeightPx = getCellSize().y;
363         allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
364 
365         if (isVerticalBarLayout()) {
366             // Always hide the Workspace text with vertical bar layout.
367             adjustToHideWorkspaceLabels();
368         }
369 
370         // Hotseat
371         if (isVerticalLayout) {
372             hotseatBarSizePx = iconSizePx + hotseatBarSidePaddingStartPx
373                     + hotseatBarSidePaddingEndPx;
374         }
375         hotseatCellHeightPx = iconSizePx;
376 
377         if (!isVerticalLayout) {
378             int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx
379                     - verticalDragHandleSizePx - edgeMarginPx;
380             float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
381             workspaceSpringLoadShrinkFactor = Math.min(
382                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f,
383                     1 - (minRequiredHeight / expectedWorkspaceHeight));
384         } else {
385             workspaceSpringLoadShrinkFactor =
386                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
387         }
388 
389         // Folder icon
390         folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
391         folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
392     }
393 
updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res)394     private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) {
395         int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
396                 + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
397                 + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
398 
399         updateFolderCellSize(1f, dm, res);
400 
401         // Don't let the folder get too close to the edges of the screen.
402         int folderMargin = edgeMarginPx * 2;
403         Point totalWorkspacePadding = getTotalWorkspacePadding();
404 
405         // Check if the icons fit within the available height.
406         float contentUsedHeight = folderCellHeightPx * inv.numFolderRows;
407         int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize
408                 - folderMargin;
409         float scaleY = contentMaxHeight / contentUsedHeight;
410 
411         // Check if the icons fit within the available width.
412         float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns;
413         int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
414         float scaleX = contentMaxWidth / contentUsedWidth;
415 
416         float scale = Math.min(scaleX, scaleY);
417         if (scale < 1f) {
418             updateFolderCellSize(scale, dm, res);
419         }
420     }
421 
updateFolderCellSize(float scale, DisplayMetrics dm, Resources res)422     private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) {
423         folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, dm) * scale);
424         folderChildTextSizePx =
425                 (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
426 
427         int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
428         int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale);
429         int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale);
430 
431         folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
432         folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
433         folderChildDrawablePaddingPx = Math.max(0,
434                 (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
435     }
436 
updateInsets(Rect insets)437     public void updateInsets(Rect insets) {
438         mInsets.set(insets);
439         updateWorkspacePadding();
440     }
441 
442     /**
443      * The current device insets. This is generally same as the insets being dispatched to
444      * {@link Insettable} elements, but can differ if the element is using a different profile.
445      */
getInsets()446     public Rect getInsets() {
447         return mInsets;
448     }
449 
getCellSize()450     public Point getCellSize() {
451         return getCellSize(inv.numColumns, inv.numRows);
452     }
453 
getCellSize(int numColumns, int numRows)454     private Point getCellSize(int numColumns, int numRows) {
455         Point result = new Point();
456         // Since we are only concerned with the overall padding, layout direction does
457         // not matter.
458         Point padding = getTotalWorkspacePadding();
459         result.x = calculateCellWidth(availableWidthPx - padding.x
460                 - cellLayoutPaddingLeftRightPx * 2, numColumns);
461         result.y = calculateCellHeight(availableHeightPx - padding.y
462                 - cellLayoutBottomPaddingPx, numRows);
463         return result;
464     }
465 
getTotalWorkspacePadding()466     public Point getTotalWorkspacePadding() {
467         updateWorkspacePadding();
468         return new Point(workspacePadding.left + workspacePadding.right,
469                 workspacePadding.top + workspacePadding.bottom);
470     }
471 
472     /**
473      * Updates {@link #workspacePadding} as a result of any internal value change to reflect the
474      * new workspace padding
475      */
updateWorkspacePadding()476     private void updateWorkspacePadding() {
477         Rect padding = workspacePadding;
478         if (isVerticalBarLayout()) {
479             padding.top = 0;
480             padding.bottom = edgeMarginPx;
481             if (isSeascape()) {
482                 padding.left = hotseatBarSizePx;
483                 padding.right = verticalDragHandleSizePx;
484             } else {
485                 padding.left = verticalDragHandleSizePx;
486                 padding.right = hotseatBarSizePx;
487             }
488         } else {
489             int paddingBottom = hotseatBarSizePx + verticalDragHandleSizePx
490                     - verticalDragHandleOverlapWorkspace;
491             if (isTablet) {
492                 // Pad the left and right of the workspace to ensure consistent spacing
493                 // between all icons
494                 // The amount of screen space available for left/right padding.
495                 int availablePaddingX = Math.max(0, widthPx - ((inv.numColumns * cellWidthPx) +
496                         ((inv.numColumns - 1) * cellWidthPx)));
497                 availablePaddingX = (int) Math.min(availablePaddingX,
498                         widthPx * MAX_HORIZONTAL_PADDING_PERCENT);
499                 int availablePaddingY = Math.max(0, heightPx - edgeMarginPx - paddingBottom
500                         - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
501                         - hotseatBarBottomPaddingPx);
502                 padding.set(availablePaddingX / 2, edgeMarginPx + availablePaddingY / 2,
503                         availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
504             } else {
505                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
506                 padding.set(desiredWorkspaceLeftRightMarginPx,
507                         edgeMarginPx,
508                         desiredWorkspaceLeftRightMarginPx,
509                         paddingBottom);
510             }
511         }
512     }
513 
getHotseatLayoutPadding()514     public Rect getHotseatLayoutPadding() {
515         if (isVerticalBarLayout()) {
516             if (isSeascape()) {
517                 mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx,
518                         mInsets.top, hotseatBarSidePaddingEndPx, mInsets.bottom);
519             } else {
520                 mHotseatPadding.set(hotseatBarSidePaddingEndPx, mInsets.top,
521                         mInsets.right + hotseatBarSidePaddingStartPx, mInsets.bottom);
522             }
523         } else {
524 
525             // We want the edges of the hotseat to line up with the edges of the workspace, but the
526             // icons in the hotseat are a different size, and so don't line up perfectly. To account
527             // for this, we pad the left and right of the hotseat with half of the difference of a
528             // workspace cell vs a hotseat cell.
529             float workspaceCellWidth = (float) widthPx / inv.numColumns;
530             float hotseatCellWidth = (float) widthPx / inv.numHotseatIcons;
531             int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
532             mHotseatPadding.set(
533                     hotseatAdjustment + workspacePadding.left + cellLayoutPaddingLeftRightPx,
534                     hotseatBarTopPaddingPx,
535                     hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx,
536                     hotseatBarBottomPaddingPx + mInsets.bottom + cellLayoutBottomPaddingPx);
537         }
538         return mHotseatPadding;
539     }
540 
541     /**
542      * @return the bounds for which the open folders should be contained within
543      */
getAbsoluteOpenFolderBounds()544     public Rect getAbsoluteOpenFolderBounds() {
545         if (isVerticalBarLayout()) {
546             // Folders should only appear right of the drop target bar and left of the hotseat
547             return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx,
548                     mInsets.top,
549                     mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx,
550                     mInsets.top + availableHeightPx);
551         } else {
552             // Folders should only appear below the drop target bar and above the hotseat
553             return new Rect(mInsets.left + edgeMarginPx,
554                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
555                     mInsets.left + availableWidthPx - edgeMarginPx,
556                     mInsets.top + availableHeightPx - hotseatBarSizePx
557                             - verticalDragHandleSizePx - edgeMarginPx);
558         }
559     }
560 
calculateCellWidth(int width, int countX)561     public static int calculateCellWidth(int width, int countX) {
562         return width / countX;
563     }
calculateCellHeight(int height, int countY)564     public static int calculateCellHeight(int height, int countY) {
565         return height / countY;
566     }
567 
568     /**
569      * When {@code true}, the device is in landscape mode and the hotseat is on the right column.
570      * When {@code false}, either device is in portrait mode or the device is in landscape mode and
571      * the hotseat is on the bottom row.
572      */
isVerticalBarLayout()573     public boolean isVerticalBarLayout() {
574         return isLandscape && transposeLayoutWithOrientation;
575     }
576 
577     /**
578      * Returns true when the number of workspace columns and all apps columns differs.
579      */
allAppsHasDifferentNumColumns()580     private boolean allAppsHasDifferentNumColumns() {
581         return inv.numAllAppsColumns != inv.numColumns;
582     }
583 
584     /**
585      * Updates orientation information and returns true if it has changed from the previous value.
586      */
updateIsSeascape(Context context)587     public boolean updateIsSeascape(Context context) {
588         if (isVerticalBarLayout()) {
589             boolean isSeascape = DefaultDisplay.INSTANCE.get(context).getInfo().rotation
590                     == Surface.ROTATION_270;
591             if (mIsSeascape != isSeascape) {
592                 mIsSeascape = isSeascape;
593                 return true;
594             }
595         }
596         return false;
597     }
598 
isSeascape()599     public boolean isSeascape() {
600         return isVerticalBarLayout() && mIsSeascape;
601     }
602 
shouldFadeAdjacentWorkspaceScreens()603     public boolean shouldFadeAdjacentWorkspaceScreens() {
604         return isVerticalBarLayout() || isLargeTablet;
605     }
606 
getCellHeight(@ontainerType int containerType)607     public int getCellHeight(@ContainerType int containerType) {
608         switch (containerType) {
609             case CellLayout.WORKSPACE:
610                 return cellHeightPx;
611             case CellLayout.FOLDER:
612                 return folderCellHeightPx;
613             case CellLayout.HOTSEAT:
614                 return hotseatCellHeightPx;
615             default:
616                 // ??
617                 return 0;
618         }
619     }
620 
getContext(Context c, int orientation)621     private static Context getContext(Context c, int orientation) {
622         Configuration context = new Configuration(c.getResources().getConfiguration());
623         context.orientation = orientation;
624         return c.createConfigurationContext(context);
625     }
626 
627     /**
628      * Callback when a component changes the DeviceProfile associated with it, as a result of
629      * configuration change
630      */
631     public interface OnDeviceProfileChangeListener {
632 
633         /**
634          * Called when the device profile is reassigned. Note that for layout and measurements, it
635          * is sufficient to listen for inset changes. Use this callback when you need to perform
636          * a one time operation.
637          */
onDeviceProfileChanged(DeviceProfile dp)638         void onDeviceProfileChanged(DeviceProfile dp);
639     }
640 }
641