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