• 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.appwidget.AppWidgetHostView;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.res.Configuration;
23 import android.content.res.Resources;
24 import android.graphics.Point;
25 import android.graphics.PointF;
26 import android.graphics.Rect;
27 import android.util.DisplayMetrics;
28 import android.view.Gravity;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.ViewGroup.LayoutParams;
32 import android.widget.FrameLayout;
33 
34 import com.android.launcher3.CellLayout.ContainerType;
35 import com.android.launcher3.badge.BadgeRenderer;
36 
37 import java.util.ArrayList;
38 
39 public class DeviceProfile {
40 
41     public interface LauncherLayoutChangeListener {
onLauncherLayoutChanged()42         void onLauncherLayoutChanged();
43     }
44 
45     public final InvariantDeviceProfile inv;
46 
47     // Device properties
48     public final boolean isTablet;
49     public final boolean isLargeTablet;
50     public final boolean isPhone;
51     public final boolean transposeLayoutWithOrientation;
52 
53     // Device properties in current orientation
54     public final boolean isLandscape;
55     public final int widthPx;
56     public final int heightPx;
57     public final int availableWidthPx;
58     public final int availableHeightPx;
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     // Overview mode
69     private final int overviewModeMinIconZoneHeightPx;
70     private final int overviewModeMaxIconZoneHeightPx;
71     private final int overviewModeBarItemWidthPx;
72     private final int overviewModeBarSpacerWidthPx;
73     private final float overviewModeIconZoneRatio;
74 
75     // Workspace
76     private final int desiredWorkspaceLeftRightMarginPx;
77     public final int cellLayoutPaddingLeftRightPx;
78     public final int cellLayoutBottomPaddingPx;
79     public final int edgeMarginPx;
80     public final Rect defaultWidgetPadding;
81     private final int defaultPageSpacingPx;
82     private final int topWorkspacePadding;
83     public float workspaceSpringLoadShrinkFactor;
84     public final int workspaceSpringLoadedBottomSpace;
85 
86     // Page indicator
87     private int pageIndicatorSizePx;
88     private final int pageIndicatorLandLeftNavBarGutterPx;
89     private final int pageIndicatorLandRightNavBarGutterPx;
90     private final int pageIndicatorLandWorkspaceOffsetPx;
91 
92     // Workspace icons
93     public int iconSizePx;
94     public int iconTextSizePx;
95     public int iconDrawablePaddingPx;
96     public int iconDrawablePaddingOriginalPx;
97 
98     public int cellWidthPx;
99     public int cellHeightPx;
100     public int workspaceCellPaddingXPx;
101 
102     // Folder
103     public int folderBackgroundOffset;
104     public int folderIconSizePx;
105     public int folderIconPreviewPadding;
106 
107     // Folder cell
108     public int folderCellWidthPx;
109     public int folderCellHeightPx;
110 
111     // Folder child
112     public int folderChildIconSizePx;
113     public int folderChildTextSizePx;
114     public int folderChildDrawablePaddingPx;
115 
116     // Hotseat
117     public int hotseatCellHeightPx;
118     // In portrait: size = height, in landscape: size = width
119     public int hotseatBarSizePx;
120     public int hotseatBarTopPaddingPx;
121     public int hotseatBarBottomPaddingPx;
122 
123     public int hotseatBarLeftNavBarLeftPaddingPx;
124     public int hotseatBarLeftNavBarRightPaddingPx;
125 
126     public int hotseatBarRightNavBarLeftPaddingPx;
127     public int hotseatBarRightNavBarRightPaddingPx;
128 
129     // All apps
130     public int allAppsCellHeightPx;
131     public int allAppsNumCols;
132     public int allAppsNumPredictiveCols;
133     public int allAppsButtonVisualSize;
134     public int allAppsIconSizePx;
135     public int allAppsIconDrawablePaddingPx;
136     public float allAppsIconTextSizePx;
137 
138     // Widgets
139     public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
140 
141     // Drop Target
142     public int dropTargetBarSizePx;
143 
144     // Insets
145     private Rect mInsets = new Rect();
146 
147     // Listeners
148     private ArrayList<LauncherLayoutChangeListener> mListeners = new ArrayList<>();
149 
150     // Icon badges
151     public BadgeRenderer mBadgeRenderer;
152 
DeviceProfile(Context context, InvariantDeviceProfile inv, Point minSize, Point maxSize, int width, int height, boolean isLandscape)153     public DeviceProfile(Context context, InvariantDeviceProfile inv,
154             Point minSize, Point maxSize,
155             int width, int height, boolean isLandscape) {
156 
157         this.inv = inv;
158         this.isLandscape = isLandscape;
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 
168         // Some more constants
169         transposeLayoutWithOrientation =
170                 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
171 
172         context = getContext(context, isVerticalBarLayout()
173                 ? Configuration.ORIENTATION_LANDSCAPE
174                 : Configuration.ORIENTATION_PORTRAIT);
175         res = context.getResources();
176 
177 
178         ComponentName cn = new ComponentName(context.getPackageName(),
179                 this.getClass().getName());
180         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
181         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
182         desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
183         cellLayoutPaddingLeftRightPx =
184                 res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
185         cellLayoutBottomPaddingPx =
186                 res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_bottom_padding);
187         pageIndicatorSizePx = res.getDimensionPixelSize(
188                 R.dimen.dynamic_grid_min_page_indicator_size);
189         pageIndicatorLandLeftNavBarGutterPx = res.getDimensionPixelSize(
190                 R.dimen.dynamic_grid_page_indicator_land_left_nav_bar_gutter_width);
191         pageIndicatorLandRightNavBarGutterPx = res.getDimensionPixelSize(
192                 R.dimen.dynamic_grid_page_indicator_land_right_nav_bar_gutter_width);
193         pageIndicatorLandWorkspaceOffsetPx =
194                 res.getDimensionPixelSize(R.dimen.all_apps_caret_workspace_offset);
195         defaultPageSpacingPx =
196                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
197         topWorkspacePadding =
198                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_top_padding);
199         overviewModeMinIconZoneHeightPx =
200                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
201         overviewModeMaxIconZoneHeightPx =
202                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
203         overviewModeBarItemWidthPx =
204                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
205         overviewModeBarSpacerWidthPx =
206                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
207         overviewModeIconZoneRatio =
208                 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
209         iconDrawablePaddingOriginalPx =
210                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
211         dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
212         workspaceSpringLoadedBottomSpace =
213                 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
214 
215         workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
216 
217         hotseatBarTopPaddingPx =
218                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
219         hotseatBarBottomPaddingPx =
220                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
221         hotseatBarLeftNavBarRightPaddingPx = res.getDimensionPixelSize(
222                 R.dimen.dynamic_grid_hotseat_land_left_nav_bar_right_padding);
223         hotseatBarRightNavBarRightPaddingPx = res.getDimensionPixelSize(
224                 R.dimen.dynamic_grid_hotseat_land_right_nav_bar_right_padding);
225         hotseatBarLeftNavBarLeftPaddingPx = res.getDimensionPixelSize(
226                 R.dimen.dynamic_grid_hotseat_land_left_nav_bar_left_padding);
227         hotseatBarRightNavBarLeftPaddingPx = res.getDimensionPixelSize(
228                 R.dimen.dynamic_grid_hotseat_land_right_nav_bar_left_padding);
229         hotseatBarSizePx = isVerticalBarLayout()
230                 ? Utilities.pxFromDp(inv.iconSize, dm)
231                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_size)
232                         + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx;
233 
234         // Determine sizes.
235         widthPx = width;
236         heightPx = height;
237         if (isLandscape) {
238             availableWidthPx = maxSize.x;
239             availableHeightPx = minSize.y;
240         } else {
241             availableWidthPx = minSize.x;
242             availableHeightPx = maxSize.y;
243         }
244 
245         // Calculate all of the remaining variables.
246         updateAvailableDimensions(dm, res);
247 
248         // Now that we have all of the variables calculated, we can tune certain sizes.
249         float aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
250         boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
251         if (!isVerticalBarLayout() && isPhone && isTallDevice) {
252             // We increase the hotseat size when there is extra space.
253             // ie. For a display with a large aspect ratio, we can keep the icons on the workspace
254             // in portrait mode closer together by adding more height to the hotseat.
255             // Note: This calculation was created after noticing a pattern in the design spec.
256             int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx;
257             hotseatBarSizePx += extraSpace - pageIndicatorSizePx;
258 
259             // Recalculate the available dimensions using the new hotseat size.
260             updateAvailableDimensions(dm, res);
261         }
262 
263         computeAllAppsButtonSize(context);
264 
265         // This is done last, after iconSizePx is calculated above.
266         mBadgeRenderer = new BadgeRenderer(context, iconSizePx);
267     }
268 
getMultiWindowProfile(Context context, Point mwSize)269     DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
270         // We take the minimum sizes of this profile and it's multi-window variant to ensure that
271         // the system decor is always excluded.
272         mwSize.set(Math.min(availableWidthPx, mwSize.x), Math.min(availableHeightPx, mwSize.y));
273 
274         // In multi-window mode, we can have widthPx = availableWidthPx
275         // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
276         // widthPx and heightPx values where it's needed.
277         DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
278                 isLandscape);
279 
280         // Hide labels on the workspace.
281         profile.adjustToHideWorkspaceLabels();
282 
283         // We use these scales to measure and layout the widgets using their full invariant profile
284         // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
285         float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
286         float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
287         profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
288 
289         return profile;
290     }
291 
addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener)292     public void addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
293         if (!mListeners.contains(listener)) {
294             mListeners.add(listener);
295         }
296     }
297 
removeLauncherLayoutChangedListener(LauncherLayoutChangeListener listener)298     public void removeLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
299         if (mListeners.contains(listener)) {
300             mListeners.remove(listener);
301         }
302     }
303 
304     /**
305      * Adjusts the profile so that the labels on the Workspace are hidden.
306      * It is important to call this method after the All Apps variables have been set.
307      */
adjustToHideWorkspaceLabels()308     private void adjustToHideWorkspaceLabels() {
309         iconTextSizePx = 0;
310         iconDrawablePaddingPx = 0;
311         cellHeightPx = iconSizePx;
312 
313         // In normal cases, All Apps cell height should equal the Workspace cell height.
314         // Since we are removing labels from the Workspace, we need to manually compute the
315         // All Apps cell height.
316         int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1);
317         allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
318                 + Utilities.calculateTextHeight(allAppsIconTextSizePx)
319                 + topBottomPadding * 2;
320     }
321 
322     /**
323      * Determine the exact visual footprint of the all apps button, taking into account scaling
324      * and internal padding of the drawable.
325      */
computeAllAppsButtonSize(Context context)326     private void computeAllAppsButtonSize(Context context) {
327         Resources res = context.getResources();
328         float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
329         allAppsButtonVisualSize = (int) (iconSizePx * (1 - padding)) - context.getResources()
330                         .getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
331     }
332 
updateAvailableDimensions(DisplayMetrics dm, Resources res)333     private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
334         updateIconSize(1f, res, dm);
335 
336         // Check to see if the icons fit within the available height.  If not, then scale down.
337         float usedHeight = (cellHeightPx * inv.numRows);
338         int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
339         if (usedHeight > maxHeight) {
340             float scale = maxHeight / usedHeight;
341             updateIconSize(scale, res, dm);
342         }
343         updateAvailableFolderCellDimensions(dm, res);
344     }
345 
updateIconSize(float scale, Resources res, DisplayMetrics dm)346     private void updateIconSize(float scale, Resources res, DisplayMetrics dm) {
347         // Workspace
348         float invIconSizePx = isVerticalBarLayout() ? inv.landscapeIconSize : inv.iconSize;
349         iconSizePx = (int) (Utilities.pxFromDp(invIconSizePx, dm) * scale);
350         iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
351         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
352 
353         cellHeightPx = iconSizePx + iconDrawablePaddingPx
354                 + Utilities.calculateTextHeight(iconTextSizePx);
355         int cellYPadding = (getCellSize().y - cellHeightPx) / 2;
356         if (iconDrawablePaddingPx > cellYPadding && !isVerticalBarLayout()
357                 && !inMultiWindowMode()) {
358             // Ensures that the label is closer to its corresponding icon. This is not an issue
359             // with vertical bar layout or multi-window mode since the issue is handled separately
360             // with their calls to {@link #adjustToHideWorkspaceLabels}.
361             cellHeightPx -= (iconDrawablePaddingPx - cellYPadding);
362             iconDrawablePaddingPx = cellYPadding;
363         }
364         cellWidthPx = iconSizePx + iconDrawablePaddingPx;
365 
366         // All apps
367         allAppsIconTextSizePx = iconTextSizePx;
368         allAppsIconSizePx = iconSizePx;
369         allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
370         allAppsCellHeightPx = getCellSize().y;
371 
372         if (isVerticalBarLayout()) {
373             // Always hide the Workspace text with vertical bar layout.
374             adjustToHideWorkspaceLabels();
375         }
376 
377         // Hotseat
378         if (isVerticalBarLayout()) {
379             hotseatBarSizePx = iconSizePx;
380         }
381         hotseatCellHeightPx = iconSizePx;
382 
383         if (!isVerticalBarLayout()) {
384             int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx
385                     - pageIndicatorSizePx - topWorkspacePadding;
386             float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
387             workspaceSpringLoadShrinkFactor = Math.min(
388                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f,
389                     1 - (minRequiredHeight / expectedWorkspaceHeight));
390         } else {
391             workspaceSpringLoadShrinkFactor =
392                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
393         }
394 
395         // Folder icon
396         folderBackgroundOffset = -iconDrawablePaddingPx;
397         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
398         folderIconPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
399     }
400 
updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res)401     private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) {
402         int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
403                 + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
404                 + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
405 
406         updateFolderCellSize(1f, dm, res);
407 
408         // Don't let the folder get too close to the edges of the screen.
409         int folderMargin = edgeMarginPx;
410 
411         // Check if the icons fit within the available height.
412         float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize;
413         int maxHeight = availableHeightPx - getTotalWorkspacePadding().y - folderMargin;
414         float scaleY = maxHeight / usedHeight;
415 
416         // Check if the icons fit within the available width.
417         float usedWidth = folderCellWidthPx * inv.numFolderColumns;
418         int maxWidth = availableWidthPx - getTotalWorkspacePadding().x - folderMargin;
419         float scaleX = maxWidth / usedWidth;
420 
421         float scale = Math.min(scaleX, scaleY);
422         if (scale < 1f) {
423             updateFolderCellSize(scale, dm, res);
424         }
425     }
426 
updateFolderCellSize(float scale, DisplayMetrics dm, Resources res)427     private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) {
428         folderChildIconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale);
429         folderChildTextSizePx =
430                 (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
431 
432         int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
433         int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale);
434         int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale);
435 
436         folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
437         folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
438         folderChildDrawablePaddingPx = Math.max(0,
439                 (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
440     }
441 
updateInsets(Rect insets)442     public void updateInsets(Rect insets) {
443         mInsets.set(insets);
444     }
445 
updateAppsViewNumCols()446     public void updateAppsViewNumCols() {
447         allAppsNumCols = allAppsNumPredictiveCols = inv.numColumns;
448     }
449 
450     /** Returns the width and height of the search bar, ignoring any padding. */
getSearchBarDimensForWidgetOpts()451     public Point getSearchBarDimensForWidgetOpts() {
452         if (isVerticalBarLayout()) {
453             return new Point(dropTargetBarSizePx, availableHeightPx - 2 * edgeMarginPx);
454         } else {
455             int gap;
456             if (isTablet) {
457                 // Pad the left and right of the workspace to ensure consistent spacing
458                 // between all icons
459                 int width = getCurrentWidth();
460                 // XXX: If the icon size changes across orientations, we will have to take
461                 //      that into account here too.
462                 gap = ((width - 2 * edgeMarginPx
463                         - (inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1)))
464                         + edgeMarginPx;
465             } else {
466                 gap = desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right;
467             }
468             return new Point(availableWidthPx - 2 * gap, dropTargetBarSizePx);
469         }
470     }
471 
getCellSize()472     public Point getCellSize() {
473         Point result = new Point();
474         // Since we are only concerned with the overall padding, layout direction does
475         // not matter.
476         Point padding = getTotalWorkspacePadding();
477         result.x = calculateCellWidth(availableWidthPx - padding.x
478                 - cellLayoutPaddingLeftRightPx * 2, inv.numColumns);
479         result.y = calculateCellHeight(availableHeightPx - padding.y
480                 - cellLayoutBottomPaddingPx, inv.numRows);
481         return result;
482     }
483 
getTotalWorkspacePadding()484     public Point getTotalWorkspacePadding() {
485         Rect padding = getWorkspacePadding(null);
486         return new Point(padding.left + padding.right, padding.top + padding.bottom);
487     }
488 
489     /**
490      * Returns the workspace padding in the specified orientation.
491      */
getWorkspacePadding(Rect recycle)492     public Rect getWorkspacePadding(Rect recycle) {
493         Rect padding = recycle == null ? new Rect() : recycle;
494         if (isVerticalBarLayout()) {
495             if (mInsets.left > 0) {
496                 padding.set(mInsets.left + pageIndicatorLandLeftNavBarGutterPx,
497                         0,
498                         hotseatBarSizePx + hotseatBarLeftNavBarRightPaddingPx
499                                 + hotseatBarLeftNavBarLeftPaddingPx
500                                 - mInsets.left,
501                         edgeMarginPx);
502             } else {
503                 padding.set(pageIndicatorLandRightNavBarGutterPx,
504                         0,
505                         hotseatBarSizePx + hotseatBarRightNavBarRightPaddingPx
506                                 + hotseatBarRightNavBarLeftPaddingPx,
507                         edgeMarginPx);
508             }
509         } else {
510             int paddingBottom = hotseatBarSizePx + pageIndicatorSizePx;
511             if (isTablet) {
512                 // Pad the left and right of the workspace to ensure consistent spacing
513                 // between all icons
514                 int width = getCurrentWidth();
515                 int height = getCurrentHeight();
516                 // The amount of screen space available for left/right padding.
517                 int availablePaddingX = Math.max(0, width - ((inv.numColumns * cellWidthPx) +
518                         ((inv.numColumns - 1) * cellWidthPx)));
519                 availablePaddingX = (int) Math.min(availablePaddingX,
520                             width * MAX_HORIZONTAL_PADDING_PERCENT);
521                 int availablePaddingY = Math.max(0, height - topWorkspacePadding - paddingBottom
522                         - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
523                         - hotseatBarBottomPaddingPx);
524                 padding.set(availablePaddingX / 2, topWorkspacePadding + availablePaddingY / 2,
525                         availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
526             } else {
527                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
528                 padding.set(desiredWorkspaceLeftRightMarginPx,
529                         topWorkspacePadding,
530                         desiredWorkspaceLeftRightMarginPx,
531                         paddingBottom);
532             }
533         }
534         return padding;
535     }
536 
537     /**
538      * @return the bounds for which the open folders should be contained within
539      */
getAbsoluteOpenFolderBounds()540     public Rect getAbsoluteOpenFolderBounds() {
541         if (isVerticalBarLayout()) {
542             // Folders should only appear right of the drop target bar and left of the hotseat
543             return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx,
544                     mInsets.top,
545                     mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx,
546                     mInsets.top + availableHeightPx);
547         } else {
548             // Folders should only appear below the drop target bar and above the hotseat
549             return new Rect(mInsets.left,
550                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
551                     mInsets.left + availableWidthPx,
552                     mInsets.top + availableHeightPx - hotseatBarSizePx
553                             - pageIndicatorSizePx - edgeMarginPx);
554         }
555     }
556 
getWorkspacePageSpacing()557     private int getWorkspacePageSpacing() {
558         if (isVerticalBarLayout() || isLargeTablet) {
559             // In landscape mode the page spacing is set to the default.
560             return defaultPageSpacingPx;
561         } else {
562             // In portrait, we want the pages spaced such that there is no
563             // overhang of the previous / next page into the current page viewport.
564             // We assume symmetrical padding in portrait mode.
565             return Math.max(defaultPageSpacingPx, getWorkspacePadding(null).left + 1);
566         }
567     }
568 
getOverviewModeButtonBarHeight()569     int getOverviewModeButtonBarHeight() {
570         int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
571         return Utilities.boundToRange(zoneHeight,
572                 overviewModeMinIconZoneHeightPx,
573                 overviewModeMaxIconZoneHeightPx);
574     }
575 
calculateCellWidth(int width, int countX)576     public static int calculateCellWidth(int width, int countX) {
577         return width / countX;
578     }
calculateCellHeight(int height, int countY)579     public static int calculateCellHeight(int height, int countY) {
580         return height / countY;
581     }
582 
583     /**
584      * When {@code true}, the device is in landscape mode and the hotseat is on the right column.
585      * When {@code false}, either device is in portrait mode or the device is in landscape mode and
586      * the hotseat is on the bottom row.
587      */
isVerticalBarLayout()588     public boolean isVerticalBarLayout() {
589         return isLandscape && transposeLayoutWithOrientation;
590     }
591 
shouldFadeAdjacentWorkspaceScreens()592     boolean shouldFadeAdjacentWorkspaceScreens() {
593         return isVerticalBarLayout() || isLargeTablet;
594     }
595 
getVisibleChildCount(ViewGroup parent)596     private int getVisibleChildCount(ViewGroup parent) {
597         int visibleChildren = 0;
598         for (int i = 0; i < parent.getChildCount(); i++) {
599             if (parent.getChildAt(i).getVisibility() != View.GONE) {
600                 visibleChildren++;
601             }
602         }
603         return visibleChildren;
604     }
605 
layout(Launcher launcher, boolean notifyListeners)606     public void layout(Launcher launcher, boolean notifyListeners) {
607         FrameLayout.LayoutParams lp;
608         boolean hasVerticalBarLayout = isVerticalBarLayout();
609 
610         // Layout the search bar space
611         Point searchBarBounds = getSearchBarDimensForWidgetOpts();
612         View searchBar = launcher.getDropTargetBar();
613         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
614         lp.width = searchBarBounds.x;
615         lp.height = searchBarBounds.y;
616         lp.topMargin = mInsets.top + edgeMarginPx;
617         searchBar.setLayoutParams(lp);
618 
619         // Layout the workspace
620         PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
621         Rect workspacePadding = getWorkspacePadding(null);
622         workspace.setPadding(workspacePadding.left, workspacePadding.top, workspacePadding.right,
623                 workspacePadding.bottom);
624         workspace.setPageSpacing(getWorkspacePageSpacing());
625 
626         // Layout the hotseat
627         Hotseat hotseat = (Hotseat) launcher.findViewById(R.id.hotseat);
628         lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
629         // We want the edges of the hotseat to line up with the edges of the workspace, but the
630         // icons in the hotseat are a different size, and so don't line up perfectly. To account for
631         // this, we pad the left and right of the hotseat with half of the difference of a workspace
632         // cell vs a hotseat cell.
633         float workspaceCellWidth = (float) getCurrentWidth() / inv.numColumns;
634         float hotseatCellWidth = (float) getCurrentWidth() / inv.numHotseatIcons;
635         int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
636         if (hasVerticalBarLayout) {
637             // Vertical hotseat -- The hotseat is fixed in the layout to be on the right of the
638             //                     screen regardless of RTL
639             int paddingRight = mInsets.left > 0
640                     ? hotseatBarLeftNavBarRightPaddingPx
641                     : hotseatBarRightNavBarRightPaddingPx;
642             int paddingLeft = mInsets.left > 0
643                     ? hotseatBarLeftNavBarLeftPaddingPx
644                     : hotseatBarRightNavBarLeftPaddingPx;
645 
646             lp.gravity = Gravity.RIGHT;
647             lp.width = hotseatBarSizePx + mInsets.left + mInsets.right
648                     + paddingLeft + paddingRight;
649             lp.height = LayoutParams.MATCH_PARENT;
650 
651             hotseat.getLayout().setPadding(mInsets.left + cellLayoutPaddingLeftRightPx
652                             + paddingLeft,
653                     mInsets.top,
654                     mInsets.right + cellLayoutPaddingLeftRightPx + paddingRight,
655                     workspacePadding.bottom + cellLayoutBottomPaddingPx);
656         } else if (isTablet) {
657             // Pad the hotseat with the workspace padding calculated above
658             lp.gravity = Gravity.BOTTOM;
659             lp.width = LayoutParams.MATCH_PARENT;
660             lp.height = hotseatBarSizePx + mInsets.bottom;
661             hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left
662                             + cellLayoutPaddingLeftRightPx,
663                     hotseatBarTopPaddingPx,
664                     hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx,
665                     hotseatBarBottomPaddingPx + mInsets.bottom + cellLayoutBottomPaddingPx);
666         } else {
667             // For phones, layout the hotseat without any bottom margin
668             // to ensure that we have space for the folders
669             lp.gravity = Gravity.BOTTOM;
670             lp.width = LayoutParams.MATCH_PARENT;
671             lp.height = hotseatBarSizePx + mInsets.bottom;
672             hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left
673                             + cellLayoutPaddingLeftRightPx,
674                     hotseatBarTopPaddingPx,
675                     hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx,
676                     hotseatBarBottomPaddingPx + mInsets.bottom + cellLayoutBottomPaddingPx);
677         }
678         hotseat.setLayoutParams(lp);
679 
680         // Layout the page indicators
681         View pageIndicator = launcher.findViewById(R.id.page_indicator);
682         if (pageIndicator != null) {
683             lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
684             if (isVerticalBarLayout()) {
685                 if (mInsets.left > 0) {
686                     lp.leftMargin = mInsets.left;
687                 } else {
688                     lp.leftMargin = pageIndicatorLandWorkspaceOffsetPx;
689                 }
690                 lp.bottomMargin = workspacePadding.bottom;
691             } else {
692                 // Put the page indicators above the hotseat
693                 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
694                 lp.height = pageIndicatorSizePx;
695                 lp.bottomMargin = hotseatBarSizePx + mInsets.bottom;
696             }
697             pageIndicator.setLayoutParams(lp);
698         }
699 
700         // Layout the Overview Mode
701         ViewGroup overviewMode = launcher.getOverviewPanel();
702         if (overviewMode != null) {
703             int visibleChildCount = getVisibleChildCount(overviewMode);
704             int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
705             int maxWidth = totalItemWidth + (visibleChildCount - 1) * overviewModeBarSpacerWidthPx;
706 
707             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
708             lp.width = Math.min(availableWidthPx, maxWidth);
709             lp.height = getOverviewModeButtonBarHeight();
710             lp.bottomMargin = mInsets.bottom;
711             overviewMode.setLayoutParams(lp);
712         }
713 
714         // Layout the AllAppsRecyclerView
715         View view = launcher.findViewById(R.id.apps_list_view);
716         int paddingLeftRight = desiredWorkspaceLeftRightMarginPx + cellLayoutPaddingLeftRightPx;
717         view.setPadding(paddingLeftRight, view.getPaddingTop(), paddingLeftRight,
718                 view.getPaddingBottom());
719 
720         if (notifyListeners) {
721             for (int i = mListeners.size() - 1; i >= 0; i--) {
722                 mListeners.get(i).onLauncherLayoutChanged();
723             }
724         }
725     }
726 
getCurrentWidth()727     private int getCurrentWidth() {
728         return isLandscape
729                 ? Math.max(widthPx, heightPx)
730                 : Math.min(widthPx, heightPx);
731     }
732 
getCurrentHeight()733     private int getCurrentHeight() {
734         return isLandscape
735                 ? Math.min(widthPx, heightPx)
736                 : Math.max(widthPx, heightPx);
737     }
738 
getCellHeight(@ontainerType int containerType)739     public int getCellHeight(@ContainerType int containerType) {
740         switch (containerType) {
741             case CellLayout.WORKSPACE:
742                 return cellHeightPx;
743             case CellLayout.FOLDER:
744                 return folderCellHeightPx;
745             case CellLayout.HOTSEAT:
746                 return hotseatCellHeightPx;
747             default:
748                 // ??
749                 return 0;
750         }
751     }
752 
753     /**
754      * @return the left/right paddings for all containers.
755      */
getContainerPadding()756     public final int[] getContainerPadding() {
757         // No paddings for portrait phone
758         if (isPhone && !isVerticalBarLayout()) {
759             return new int[] {0, 0};
760         }
761 
762         // In landscape, we match the width of the workspace
763         Rect padding = getWorkspacePadding(null);
764         return new int[] { padding.left - mInsets.left, padding.right + mInsets.left};
765     }
766 
inMultiWindowMode()767     public boolean inMultiWindowMode() {
768         return this != inv.landscapeProfile && this != inv.portraitProfile;
769     }
770 
shouldIgnoreLongPressToOverview(float touchX)771     public boolean shouldIgnoreLongPressToOverview(float touchX) {
772         boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeMarginPx;
773         boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeMarginPx);
774         return !inMultiWindowMode() && (touchedLhsEdge || touchedRhsEdge);
775     }
776 
getContext(Context c, int orientation)777     private static Context getContext(Context c, int orientation) {
778         Configuration context = new Configuration(c.getResources().getConfiguration());
779         context.orientation = orientation;
780         return c.createConfigurationContext(context);
781 
782     }
783 }
784