• 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.Resources;
23 import android.graphics.Paint;
24 import android.graphics.Paint.FontMetrics;
25 import android.graphics.Point;
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.view.ViewGroup.MarginLayoutParams;
33 import android.widget.FrameLayout;
34 import android.widget.LinearLayout;
35 
36 public class DeviceProfile {
37 
38     public final InvariantDeviceProfile inv;
39 
40     // Device properties
41     public final boolean isTablet;
42     public final boolean isLargeTablet;
43     public final boolean isPhone;
44     public final boolean transposeLayoutWithOrientation;
45 
46     // Device properties in current orientation
47     public final boolean isLandscape;
48     public final int widthPx;
49     public final int heightPx;
50     public final int availableWidthPx;
51     public final int availableHeightPx;
52 
53     // Overview mode
54     private final int overviewModeMinIconZoneHeightPx;
55     private final int overviewModeMaxIconZoneHeightPx;
56     private final int overviewModeBarItemWidthPx;
57     private final int overviewModeBarSpacerWidthPx;
58     private final float overviewModeIconZoneRatio;
59 
60     // Workspace
61     private int desiredWorkspaceLeftRightMarginPx;
62     public final int edgeMarginPx;
63     public final Rect defaultWidgetPadding;
64     private final int pageIndicatorHeightPx;
65     private final int defaultPageSpacingPx;
66     private float dragViewScale;
67 
68     // Workspace icons
69     public int iconSizePx;
70     public int iconTextSizePx;
71     public int iconDrawablePaddingPx;
72     public int iconDrawablePaddingOriginalPx;
73 
74     public int cellWidthPx;
75     public int cellHeightPx;
76 
77     // Folder
78     public int folderBackgroundOffset;
79     public int folderIconSizePx;
80     public int folderCellWidthPx;
81     public int folderCellHeightPx;
82 
83     // Hotseat
84     public int hotseatCellWidthPx;
85     public int hotseatCellHeightPx;
86     public int hotseatIconSizePx;
87     private int hotseatBarHeightPx;
88 
89     // All apps
90     public int allAppsNumCols;
91     public int allAppsNumPredictiveCols;
92     public int allAppsButtonVisualSize;
93     public final int allAppsIconSizePx;
94     public final int allAppsIconTextSizePx;
95 
96     // QSB
97     private int searchBarSpaceWidthPx;
98     private int searchBarSpaceHeightPx;
99 
DeviceProfile(Context context, InvariantDeviceProfile inv, Point minSize, Point maxSize, int width, int height, boolean isLandscape)100     public DeviceProfile(Context context, InvariantDeviceProfile inv,
101             Point minSize, Point maxSize,
102             int width, int height, boolean isLandscape) {
103 
104         this.inv = inv;
105         this.isLandscape = isLandscape;
106 
107         Resources res = context.getResources();
108         DisplayMetrics dm = res.getDisplayMetrics();
109 
110         // Constants from resources
111         isTablet = res.getBoolean(R.bool.is_tablet);
112         isLargeTablet = res.getBoolean(R.bool.is_large_tablet);
113         isPhone = !isTablet && !isLargeTablet;
114 
115         // Some more constants
116         transposeLayoutWithOrientation =
117                 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
118 
119         ComponentName cn = new ComponentName(context.getPackageName(),
120                 this.getClass().getName());
121         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
122         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
123         desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
124         pageIndicatorHeightPx =
125                 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
126         defaultPageSpacingPx =
127                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
128         overviewModeMinIconZoneHeightPx =
129                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
130         overviewModeMaxIconZoneHeightPx =
131                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
132         overviewModeBarItemWidthPx =
133                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
134         overviewModeBarSpacerWidthPx =
135                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
136         overviewModeIconZoneRatio =
137                 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
138         iconDrawablePaddingOriginalPx =
139                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
140 
141         // AllApps uses the original non-scaled icon text size
142         allAppsIconTextSizePx = Utilities.pxFromDp(inv.iconTextSize, dm);
143 
144         // AllApps uses the original non-scaled icon size
145         allAppsIconSizePx = Utilities.pxFromDp(inv.iconSize, dm);
146 
147         // Determine sizes.
148         widthPx = width;
149         heightPx = height;
150         if (isLandscape) {
151             availableWidthPx = maxSize.x;
152             availableHeightPx = minSize.y;
153         } else {
154             availableWidthPx = minSize.x;
155             availableHeightPx = maxSize.y;
156         }
157 
158         // Calculate the remaining vars
159         updateAvailableDimensions(dm, res);
160         computeAllAppsButtonSize(context);
161     }
162 
163     /**
164      * Determine the exact visual footprint of the all apps button, taking into account scaling
165      * and internal padding of the drawable.
166      */
computeAllAppsButtonSize(Context context)167     private void computeAllAppsButtonSize(Context context) {
168         Resources res = context.getResources();
169         float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
170         allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding));
171     }
172 
updateAvailableDimensions(DisplayMetrics dm, Resources res)173     private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
174         // Check to see if the icons fit in the new available height.  If not, then we need to
175         // shrink the icon size.
176         float scale = 1f;
177         int drawablePadding = iconDrawablePaddingOriginalPx;
178         updateIconSize(1f, drawablePadding, res, dm);
179         float usedHeight = (cellHeightPx * inv.numRows);
180 
181         // We only care about the top and bottom workspace padding, which is not affected by RTL.
182         Rect workspacePadding = getWorkspacePadding(false /* isLayoutRtl */);
183         int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
184         if (usedHeight > maxHeight) {
185             scale = maxHeight / usedHeight;
186             drawablePadding = 0;
187         }
188         updateIconSize(scale, drawablePadding, res, dm);
189     }
190 
updateIconSize(float scale, int drawablePadding, Resources res, DisplayMetrics dm)191     private void updateIconSize(float scale, int drawablePadding, Resources res,
192                                 DisplayMetrics dm) {
193         iconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale);
194         iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
195         iconDrawablePaddingPx = drawablePadding;
196         hotseatIconSizePx = (int) (Utilities.pxFromDp(inv.hotseatIconSize, dm) * scale);
197 
198         // Search Bar
199         searchBarSpaceWidthPx = Math.min(widthPx,
200                 res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width));
201         searchBarSpaceHeightPx = getSearchBarTopOffset()
202                 + res.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
203 
204         // Calculate the actual text height
205         Paint textPaint = new Paint();
206         textPaint.setTextSize(iconTextSizePx);
207         FontMetrics fm = textPaint.getFontMetrics();
208         cellWidthPx = iconSizePx;
209         cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
210         final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
211         dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
212 
213         // Hotseat
214         hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
215         hotseatCellWidthPx = iconSizePx;
216         hotseatCellHeightPx = iconSizePx;
217 
218         // Folder
219         folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
220         folderCellHeightPx = cellHeightPx + edgeMarginPx;
221         folderBackgroundOffset = -edgeMarginPx;
222         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
223     }
224 
225     /**
226      * @param recyclerViewWidth the available width of the AllAppsRecyclerView
227      */
updateAppsViewNumCols(Resources res, int recyclerViewWidth)228     public void updateAppsViewNumCols(Resources res, int recyclerViewWidth) {
229         int appsViewLeftMarginPx =
230                 res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
231         int allAppsCellWidthGap =
232                 res.getDimensionPixelSize(R.dimen.all_apps_icon_width_gap);
233         int availableAppsWidthPx = (recyclerViewWidth > 0) ? recyclerViewWidth : availableWidthPx;
234         int numAppsCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
235                 (allAppsIconSizePx + allAppsCellWidthGap);
236         int numPredictiveAppCols = Math.max(inv.minAllAppsPredictionColumns, numAppsCols);
237         allAppsNumCols = numAppsCols;
238         allAppsNumPredictiveCols = numPredictiveAppCols;
239     }
240 
241     /** Returns the search bar top offset */
getSearchBarTopOffset()242     private int getSearchBarTopOffset() {
243         if (isTablet && !isVerticalBarLayout()) {
244             return 4 * edgeMarginPx;
245         } else {
246             return 2 * edgeMarginPx;
247         }
248     }
249 
250     /** Returns the search bar bounds in the current orientation */
getSearchBarBounds(boolean isLayoutRtl)251     public Rect getSearchBarBounds(boolean isLayoutRtl) {
252         Rect bounds = new Rect();
253         if (isLandscape && transposeLayoutWithOrientation) {
254             if (isLayoutRtl) {
255                 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
256                         availableWidthPx, availableHeightPx - edgeMarginPx);
257             } else {
258                 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
259                         availableHeightPx - edgeMarginPx);
260             }
261         } else {
262             if (isTablet) {
263                 // Pad the left and right of the workspace to ensure consistent spacing
264                 // between all icons
265                 int width = getCurrentWidth();
266                 // XXX: If the icon size changes across orientations, we will have to take
267                 //      that into account here too.
268                 int gap = (int) ((width - 2 * edgeMarginPx -
269                         (inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1)));
270                 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
271                         availableWidthPx - (edgeMarginPx + gap),
272                         searchBarSpaceHeightPx);
273             } else {
274                 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
275                         getSearchBarTopOffset(),
276                         availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
277                         defaultWidgetPadding.right), searchBarSpaceHeightPx);
278             }
279         }
280         return bounds;
281     }
282 
283     /** Returns the workspace padding in the specified orientation */
getWorkspacePadding(boolean isLayoutRtl)284     Rect getWorkspacePadding(boolean isLayoutRtl) {
285         Rect searchBarBounds = getSearchBarBounds(isLayoutRtl);
286         Rect padding = new Rect();
287         if (isLandscape && transposeLayoutWithOrientation) {
288             // Pad the left and right of the workspace with search/hotseat bar sizes
289             if (isLayoutRtl) {
290                 padding.set(hotseatBarHeightPx, edgeMarginPx,
291                         searchBarBounds.width(), edgeMarginPx);
292             } else {
293                 padding.set(searchBarBounds.width(), edgeMarginPx,
294                         hotseatBarHeightPx, edgeMarginPx);
295             }
296         } else {
297             if (isTablet) {
298                 // Pad the left and right of the workspace to ensure consistent spacing
299                 // between all icons
300                 float gapScale = 1f + (dragViewScale - 1f) / 2f;
301                 int width = getCurrentWidth();
302                 int height = getCurrentHeight();
303                 int paddingTop = searchBarBounds.bottom;
304                 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
305                 int availableWidth = Math.max(0, width - (int) ((inv.numColumns * cellWidthPx) +
306                         (inv.numColumns * gapScale * cellWidthPx)));
307                 int availableHeight = Math.max(0, height - paddingTop - paddingBottom
308                         - (int) (2 * inv.numRows * cellHeightPx));
309                 padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
310                         availableWidth / 2, paddingBottom + availableHeight / 2);
311             } else {
312                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
313                 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
314                         searchBarBounds.bottom,
315                         desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
316                         hotseatBarHeightPx + pageIndicatorHeightPx);
317             }
318         }
319         return padding;
320     }
321 
getWorkspacePageSpacing(boolean isLayoutRtl)322     private int getWorkspacePageSpacing(boolean isLayoutRtl) {
323         if ((isLandscape && transposeLayoutWithOrientation) || isLargeTablet) {
324             // In landscape mode the page spacing is set to the default.
325             return defaultPageSpacingPx;
326         } else {
327             // In portrait, we want the pages spaced such that there is no
328             // overhang of the previous / next page into the current page viewport.
329             // We assume symmetrical padding in portrait mode.
330             return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding(isLayoutRtl).left);
331         }
332     }
333 
getOverviewModeButtonBarHeight()334     int getOverviewModeButtonBarHeight() {
335         int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
336         zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
337                 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
338         return zoneHeight;
339     }
340 
341     // The rect returned will be extended to below the system ui that covers the workspace
getHotseatRect()342     Rect getHotseatRect() {
343         if (isVerticalBarLayout()) {
344             return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
345                     Integer.MAX_VALUE, availableHeightPx);
346         } else {
347             return new Rect(0, availableHeightPx - hotseatBarHeightPx,
348                     availableWidthPx, Integer.MAX_VALUE);
349         }
350     }
351 
calculateCellWidth(int width, int countX)352     public static int calculateCellWidth(int width, int countX) {
353         return width / countX;
354     }
calculateCellHeight(int height, int countY)355     public static int calculateCellHeight(int height, int countY) {
356         return height / countY;
357     }
358 
359     /**
360      * When {@code true}, hotseat is on the bottom row when in landscape mode.
361      * If {@code false}, hotseat is on the right column when in landscape mode.
362      */
isVerticalBarLayout()363     boolean isVerticalBarLayout() {
364         return isLandscape && transposeLayoutWithOrientation;
365     }
366 
shouldFadeAdjacentWorkspaceScreens()367     boolean shouldFadeAdjacentWorkspaceScreens() {
368         return isVerticalBarLayout() || isLargeTablet;
369     }
370 
getVisibleChildCount(ViewGroup parent)371     private int getVisibleChildCount(ViewGroup parent) {
372         int visibleChildren = 0;
373         for (int i = 0; i < parent.getChildCount(); i++) {
374             if (parent.getChildAt(i).getVisibility() != View.GONE) {
375                 visibleChildren++;
376             }
377         }
378         return visibleChildren;
379     }
380 
layout(Launcher launcher)381     public void layout(Launcher launcher) {
382         FrameLayout.LayoutParams lp;
383         boolean hasVerticalBarLayout = isVerticalBarLayout();
384         final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
385 
386         // Layout the search bar space
387         View searchBar = launcher.getSearchDropTargetBar();
388         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
389         if (hasVerticalBarLayout) {
390             // Vertical search bar space -- The search bar is fixed in the layout to be on the left
391             //                              of the screen regardless of RTL
392             lp.gravity = Gravity.LEFT;
393             lp.width = searchBarSpaceHeightPx;
394 
395             LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
396             targets.setOrientation(LinearLayout.VERTICAL);
397             FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams();
398             targetsLp.gravity = Gravity.TOP;
399             targetsLp.height = LayoutParams.WRAP_CONTENT;
400 
401         } else {
402             // Horizontal search bar space
403             lp.gravity = Gravity.TOP;
404             lp.height = searchBarSpaceHeightPx;
405 
406             LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
407             targets.getLayoutParams().width = searchBarSpaceWidthPx;
408         }
409         searchBar.setLayoutParams(lp);
410 
411         // Layout the workspace
412         PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
413         lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
414         lp.gravity = Gravity.CENTER;
415         Rect padding = getWorkspacePadding(isLayoutRtl);
416         workspace.setLayoutParams(lp);
417         workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
418         workspace.setPageSpacing(getWorkspacePageSpacing(isLayoutRtl));
419 
420         // Layout the hotseat
421         View hotseat = launcher.findViewById(R.id.hotseat);
422         lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
423         if (hasVerticalBarLayout) {
424             // Vertical hotseat -- The hotseat is fixed in the layout to be on the right of the
425             //                     screen regardless of RTL
426             lp.gravity = Gravity.RIGHT;
427             lp.width = hotseatBarHeightPx;
428             lp.height = LayoutParams.MATCH_PARENT;
429             hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
430         } else if (isTablet) {
431             // Pad the hotseat with the workspace padding calculated above
432             lp.gravity = Gravity.BOTTOM;
433             lp.width = LayoutParams.MATCH_PARENT;
434             lp.height = hotseatBarHeightPx;
435             hotseat.setPadding(edgeMarginPx + padding.left, 0,
436                     edgeMarginPx + padding.right,
437                     2 * edgeMarginPx);
438         } else {
439             // For phones, layout the hotseat without any bottom margin
440             // to ensure that we have space for the folders
441             lp.gravity = Gravity.BOTTOM;
442             lp.width = LayoutParams.MATCH_PARENT;
443             lp.height = hotseatBarHeightPx;
444             hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
445                     2 * edgeMarginPx, 0);
446         }
447         hotseat.setLayoutParams(lp);
448 
449         // Layout the page indicators
450         View pageIndicator = launcher.findViewById(R.id.page_indicator);
451         if (pageIndicator != null) {
452             if (hasVerticalBarLayout) {
453                 // Hide the page indicators when we have vertical search/hotseat
454                 pageIndicator.setVisibility(View.GONE);
455             } else {
456                 // Put the page indicators above the hotseat
457                 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
458                 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
459                 lp.width = LayoutParams.WRAP_CONTENT;
460                 lp.height = LayoutParams.WRAP_CONTENT;
461                 lp.bottomMargin = hotseatBarHeightPx;
462                 pageIndicator.setLayoutParams(lp);
463             }
464         }
465 
466         // Layout the Overview Mode
467         ViewGroup overviewMode = launcher.getOverviewPanel();
468         if (overviewMode != null) {
469             int overviewButtonBarHeight = getOverviewModeButtonBarHeight();
470             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
471             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
472 
473             int visibleChildCount = getVisibleChildCount(overviewMode);
474             int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
475             int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
476 
477             lp.width = Math.min(availableWidthPx, maxWidth);
478             lp.height = overviewButtonBarHeight;
479             overviewMode.setLayoutParams(lp);
480 
481             if (lp.width > totalItemWidth && visibleChildCount > 1) {
482                 // We have enough space. Lets add some margin too.
483                 int margin = (lp.width - totalItemWidth) / (visibleChildCount-1);
484                 View lastChild = null;
485 
486                 // Set margin of all visible children except the last visible child
487                 for (int i = 0; i < visibleChildCount; i++) {
488                     if (lastChild != null) {
489                         MarginLayoutParams clp = (MarginLayoutParams) lastChild.getLayoutParams();
490                         if (isLayoutRtl) {
491                             clp.leftMargin = margin;
492                         } else {
493                             clp.rightMargin = margin;
494                         }
495                         lastChild.setLayoutParams(clp);
496                         lastChild = null;
497                     }
498                     View thisChild = overviewMode.getChildAt(i);
499                     if (thisChild.getVisibility() != View.GONE) {
500                         lastChild = thisChild;
501                     }
502                 }
503             }
504         }
505     }
506 
getCurrentWidth()507     private int getCurrentWidth() {
508         return isLandscape
509                 ? Math.max(widthPx, heightPx)
510                 : Math.min(widthPx, heightPx);
511     }
512 
getCurrentHeight()513     private int getCurrentHeight() {
514         return isLandscape
515                 ? Math.min(widthPx, heightPx)
516                 : Math.max(widthPx, heightPx);
517     }
518 }
519