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