DeviceProfile.java revision 6033ceb438d2d8e6c8406e2b61ae515e6156965c
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.Configuration;
23import android.content.res.Resources;
24import android.graphics.Paint;
25import android.graphics.Paint.FontMetrics;
26import android.graphics.Point;
27import android.graphics.PointF;
28import android.graphics.Rect;
29import android.util.DisplayMetrics;
30import android.view.Display;
31import android.view.Gravity;
32import android.view.Surface;
33import android.view.View;
34import android.view.ViewGroup;
35import android.view.ViewGroup.LayoutParams;
36import android.view.WindowManager;
37import android.widget.FrameLayout;
38import android.widget.LinearLayout;
39
40import java.util.ArrayList;
41import java.util.Collections;
42import java.util.Comparator;
43
44
45class DeviceProfileQuery {
46    float widthDps;
47    float heightDps;
48    float value;
49    PointF dimens;
50
51    DeviceProfileQuery(float w, float h, float v) {
52        widthDps = w;
53        heightDps = h;
54        value = v;
55        dimens = new PointF(w, h);
56    }
57}
58
59public class DeviceProfile {
60    public static interface DeviceProfileCallbacks {
61        public void onAvailableSizeChanged(DeviceProfile grid);
62    }
63
64    String name;
65    float minWidthDps;
66    float minHeightDps;
67    float numRows;
68    float numColumns;
69    float numHotseatIcons;
70    private float iconSize;
71    private float iconTextSize;
72    private int iconDrawablePaddingOriginalPx;
73    private float hotseatIconSize;
74
75    boolean isLandscape;
76    boolean isTablet;
77    boolean isLargeTablet;
78    boolean isLayoutRtl;
79    boolean transposeLayoutWithOrientation;
80
81    int desiredWorkspaceLeftRightMarginPx;
82    int edgeMarginPx;
83    Rect defaultWidgetPadding;
84
85    int widthPx;
86    int heightPx;
87    int availableWidthPx;
88    int availableHeightPx;
89    int defaultPageSpacingPx;
90
91    int overviewModeMinIconZoneHeightPx;
92    int overviewModeMaxIconZoneHeightPx;
93    int overviewModeBarItemWidthPx;
94    int overviewModeBarSpacerWidthPx;
95    float overviewModeIconZoneRatio;
96    float overviewModeScaleFactor;
97
98    int iconSizePx;
99    int iconTextSizePx;
100    int iconDrawablePaddingPx;
101    int cellWidthPx;
102    int cellHeightPx;
103    int allAppsIconSizePx;
104    int allAppsIconTextSizePx;
105    int allAppsCellWidthPx;
106    int allAppsCellHeightPx;
107    int allAppsCellPaddingPx;
108    int folderBackgroundOffset;
109    int folderIconSizePx;
110    int folderCellWidthPx;
111    int folderCellHeightPx;
112    int hotseatCellWidthPx;
113    int hotseatCellHeightPx;
114    int hotseatIconSizePx;
115    int hotseatBarHeightPx;
116    int hotseatAllAppsRank;
117    int allAppsNumRows;
118    int allAppsNumCols;
119    int searchBarSpaceWidthPx;
120    int searchBarSpaceMaxWidthPx;
121    int searchBarSpaceHeightPx;
122    int searchBarHeightPx;
123    int pageIndicatorHeightPx;
124
125    float dragViewScale;
126
127    private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
128
129    DeviceProfile(String n, float w, float h, float r, float c,
130                  float is, float its, float hs, float his) {
131        // Ensure that we have an odd number of hotseat items (since we need to place all apps)
132        if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) {
133            throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
134        }
135
136        name = n;
137        minWidthDps = w;
138        minHeightDps = h;
139        numRows = r;
140        numColumns = c;
141        iconSize = is;
142        iconTextSize = its;
143        numHotseatIcons = hs;
144        hotseatIconSize = his;
145    }
146
147    DeviceProfile(Context context,
148                  ArrayList<DeviceProfile> profiles,
149                  float minWidth, float minHeight,
150                  int wPx, int hPx,
151                  int awPx, int ahPx,
152                  Resources res) {
153        DisplayMetrics dm = res.getDisplayMetrics();
154        ArrayList<DeviceProfileQuery> points =
155                new ArrayList<DeviceProfileQuery>();
156        transposeLayoutWithOrientation =
157                res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
158        minWidthDps = minWidth;
159        minHeightDps = minHeight;
160
161        ComponentName cn = new ComponentName(context.getPackageName(),
162                this.getClass().getName());
163        defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
164        edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
165        desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
166        pageIndicatorHeightPx =
167                res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
168        defaultPageSpacingPx =
169                res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
170        allAppsCellPaddingPx =
171                res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
172        overviewModeMinIconZoneHeightPx =
173                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
174        overviewModeMaxIconZoneHeightPx =
175                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
176        overviewModeBarItemWidthPx =
177                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
178        overviewModeBarSpacerWidthPx =
179                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
180        overviewModeIconZoneRatio =
181                res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
182        overviewModeScaleFactor =
183                res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
184
185        // Interpolate the rows
186        for (DeviceProfile p : profiles) {
187            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
188        }
189        numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
190        // Interpolate the columns
191        points.clear();
192        for (DeviceProfile p : profiles) {
193            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
194        }
195        numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
196        // Interpolate the hotseat length
197        points.clear();
198        for (DeviceProfile p : profiles) {
199            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
200        }
201        numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
202        hotseatAllAppsRank = (int) (numHotseatIcons / 2);
203
204        // Interpolate the icon size
205        points.clear();
206        for (DeviceProfile p : profiles) {
207            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
208        }
209        iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
210        // AllApps uses the original non-scaled icon size
211        allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
212
213        // Interpolate the icon text size
214        points.clear();
215        for (DeviceProfile p : profiles) {
216            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
217        }
218        iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
219        iconDrawablePaddingOriginalPx =
220                res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
221        // AllApps uses the original non-scaled icon text size
222        allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
223
224        // Interpolate the hotseat icon size
225        points.clear();
226        for (DeviceProfile p : profiles) {
227            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
228        }
229        // Hotseat
230        hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
231
232        // Calculate the remaining vars
233        updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
234        updateAvailableDimensions(context);
235    }
236
237    void addCallback(DeviceProfileCallbacks cb) {
238        mCallbacks.add(cb);
239        cb.onAvailableSizeChanged(this);
240    }
241    void removeCallback(DeviceProfileCallbacks cb) {
242        mCallbacks.remove(cb);
243    }
244
245    private int getDeviceOrientation(Context context) {
246        WindowManager windowManager =  (WindowManager)
247                context.getSystemService(Context.WINDOW_SERVICE);
248        Resources resources = context.getResources();
249        DisplayMetrics dm = resources.getDisplayMetrics();
250        Configuration config = resources.getConfiguration();
251        int rotation = windowManager.getDefaultDisplay().getRotation();
252
253        boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
254                (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
255        boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
256                (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
257        if (isLandscape || isRotatedPortrait) {
258            return CellLayout.LANDSCAPE;
259        } else {
260            return CellLayout.PORTRAIT;
261        }
262    }
263
264    private void updateAvailableDimensions(Context context) {
265        WindowManager windowManager =  (WindowManager)
266                context.getSystemService(Context.WINDOW_SERVICE);
267        Display display = windowManager.getDefaultDisplay();
268        Resources resources = context.getResources();
269        DisplayMetrics dm = resources.getDisplayMetrics();
270        Configuration config = resources.getConfiguration();
271
272        // There are three possible configurations that the dynamic grid accounts for, portrait,
273        // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
274        // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
275        // the height is the smallest height (either with the nav bar at the bottom or to the
276        // side) and otherwise, the height is simply the largest possible height for a portrait
277        // device.
278        Point size = new Point();
279        Point smallestSize = new Point();
280        Point largestSize = new Point();
281        display.getSize(size);
282        display.getCurrentSizeRange(smallestSize, largestSize);
283        availableWidthPx = size.x;
284        if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
285            availableHeightPx = smallestSize.y;
286        } else {
287            availableHeightPx = largestSize.y;
288        }
289
290        // Check to see if the icons fit in the new available height.  If not, then we need to
291        // shrink the icon size.
292        float scale = 1f;
293        int drawablePadding = iconDrawablePaddingOriginalPx;
294        updateIconSize(1f, drawablePadding, resources, dm);
295        float usedHeight = (cellHeightPx * numRows);
296
297        Rect workspacePadding = getWorkspacePadding();
298        int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
299        if (usedHeight > maxHeight) {
300            scale = maxHeight / usedHeight;
301            drawablePadding = 0;
302        }
303        updateIconSize(scale, drawablePadding, resources, dm);
304
305        // Make the callbacks
306        for (DeviceProfileCallbacks cb : mCallbacks) {
307            cb.onAvailableSizeChanged(this);
308        }
309    }
310
311    private void updateIconSize(float scale, int drawablePadding, Resources resources,
312                                DisplayMetrics dm) {
313        iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
314        iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
315        iconDrawablePaddingPx = drawablePadding;
316        hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
317
318        // Search Bar
319        searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
320        searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
321        searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
322        searchBarSpaceHeightPx = searchBarHeightPx + getSearchBarTopOffset();
323
324        // Calculate the actual text height
325        Paint textPaint = new Paint();
326        textPaint.setTextSize(iconTextSizePx);
327        FontMetrics fm = textPaint.getFontMetrics();
328        cellWidthPx = iconSizePx;
329        cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
330        final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale);
331        dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
332
333        // Hotseat
334        hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
335        hotseatCellWidthPx = iconSizePx;
336        hotseatCellHeightPx = iconSizePx;
337
338        // Folder
339        folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
340        folderCellHeightPx = cellHeightPx + edgeMarginPx;
341        folderBackgroundOffset = -edgeMarginPx;
342        folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
343
344        // All Apps
345        Rect padding = getWorkspacePadding(isLandscape ?
346                CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
347        int pageIndicatorOffset =
348                resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
349        allAppsCellWidthPx = allAppsIconSizePx;
350        allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
351        int maxLongEdgeCellCount =
352                resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
353        int maxShortEdgeCellCount =
354                resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
355        int minEdgeCellCount =
356                resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
357        int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
358        int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
359
360        allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
361                (allAppsCellHeightPx + allAppsCellPaddingPx);
362        allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
363        allAppsNumCols = (availableWidthPx) /
364                (allAppsCellWidthPx + allAppsCellPaddingPx);
365        allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
366    }
367
368    void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
369                                 int awPx, int ahPx) {
370        Configuration configuration = resources.getConfiguration();
371        isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
372        isTablet = resources.getBoolean(R.bool.is_tablet);
373        isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
374        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
375            isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
376        } else {
377            isLayoutRtl = false;
378        }
379        widthPx = wPx;
380        heightPx = hPx;
381        availableWidthPx = awPx;
382        availableHeightPx = ahPx;
383
384        updateAvailableDimensions(context);
385    }
386
387    private float dist(PointF p0, PointF p1) {
388        return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
389                (p1.y-p0.y)*(p1.y-p0.y));
390    }
391
392    private float weight(PointF a, PointF b,
393                        float pow) {
394        float d = dist(a, b);
395        if (d == 0f) {
396            return Float.POSITIVE_INFINITY;
397        }
398        return (float) (1f / Math.pow(d, pow));
399    }
400
401    private float invDistWeightedInterpolate(float width, float height,
402                ArrayList<DeviceProfileQuery> points) {
403        float sum = 0;
404        float weights = 0;
405        float pow = 5;
406        float kNearestNeighbors = 3;
407        final PointF xy = new PointF(width, height);
408
409        ArrayList<DeviceProfileQuery> pointsByNearness = points;
410        Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
411            public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
412                return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
413            }
414        });
415
416        for (int i = 0; i < pointsByNearness.size(); ++i) {
417            DeviceProfileQuery p = pointsByNearness.get(i);
418            if (i < kNearestNeighbors) {
419                float w = weight(xy, p.dimens, pow);
420                if (w == Float.POSITIVE_INFINITY) {
421                    return p.value;
422                }
423                weights += w;
424            }
425        }
426
427        for (int i = 0; i < pointsByNearness.size(); ++i) {
428            DeviceProfileQuery p = pointsByNearness.get(i);
429            if (i < kNearestNeighbors) {
430                float w = weight(xy, p.dimens, pow);
431                sum += w * p.value / weights;
432            }
433        }
434
435        return sum;
436    }
437
438    /** Returns the search bar top offset */
439    int getSearchBarTopOffset() {
440        if (isTablet() && !isVerticalBarLayout()) {
441            return 4 * edgeMarginPx;
442        } else {
443            return 2 * edgeMarginPx;
444        }
445    }
446
447    /** Returns the search bar bounds in the current orientation */
448    Rect getSearchBarBounds() {
449        return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
450    }
451    /** Returns the search bar bounds in the specified orientation */
452    Rect getSearchBarBounds(int orientation) {
453        Rect bounds = new Rect();
454        if (orientation == CellLayout.LANDSCAPE &&
455                transposeLayoutWithOrientation) {
456            if (isLayoutRtl) {
457                bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
458                        availableWidthPx, availableHeightPx - edgeMarginPx);
459            } else {
460                bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
461                        availableHeightPx - edgeMarginPx);
462            }
463        } else {
464            if (isTablet()) {
465                // Pad the left and right of the workspace to ensure consistent spacing
466                // between all icons
467                int width = (orientation == CellLayout.LANDSCAPE)
468                        ? Math.max(widthPx, heightPx)
469                        : Math.min(widthPx, heightPx);
470                // XXX: If the icon size changes across orientations, we will have to take
471                //      that into account here too.
472                int gap = (int) ((width - 2 * edgeMarginPx -
473                        (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
474                bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
475                        availableWidthPx - (edgeMarginPx + gap),
476                        searchBarSpaceHeightPx);
477            } else {
478                bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
479                        getSearchBarTopOffset(),
480                        availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
481                        defaultWidgetPadding.right), searchBarSpaceHeightPx);
482            }
483        }
484        return bounds;
485    }
486
487    /** Returns the bounds of the workspace page indicators. */
488    Rect getWorkspacePageIndicatorBounds(Rect insets) {
489        Rect workspacePadding = getWorkspacePadding();
490        if (isLandscape && transposeLayoutWithOrientation) {
491            if (isLayoutRtl) {
492                return new Rect(workspacePadding.left, workspacePadding.top,
493                        workspacePadding.left + pageIndicatorHeightPx,
494                        heightPx - workspacePadding.bottom - insets.bottom);
495            } else {
496                int pageIndicatorLeft = widthPx - workspacePadding.right;
497                return new Rect(pageIndicatorLeft, workspacePadding.top,
498                        pageIndicatorLeft + pageIndicatorHeightPx,
499                        heightPx - workspacePadding.bottom - insets.bottom);
500            }
501        } else {
502            int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom;
503            return new Rect(workspacePadding.left, pageIndicatorTop,
504                    widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx);
505        }
506    }
507
508    /** Returns the workspace padding in the specified orientation */
509    Rect getWorkspacePadding() {
510        return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
511    }
512    Rect getWorkspacePadding(int orientation) {
513        Rect searchBarBounds = getSearchBarBounds(orientation);
514        Rect padding = new Rect();
515        if (orientation == CellLayout.LANDSCAPE &&
516                transposeLayoutWithOrientation) {
517            // Pad the left and right of the workspace with search/hotseat bar sizes
518            if (isLayoutRtl) {
519                padding.set(hotseatBarHeightPx, edgeMarginPx,
520                        searchBarBounds.width(), edgeMarginPx);
521            } else {
522                padding.set(searchBarBounds.width(), edgeMarginPx,
523                        hotseatBarHeightPx, edgeMarginPx);
524            }
525        } else {
526            if (isTablet()) {
527                // Pad the left and right of the workspace to ensure consistent spacing
528                // between all icons
529                float gapScale = 1f + (dragViewScale - 1f) / 2f;
530                int width = (orientation == CellLayout.LANDSCAPE)
531                        ? Math.max(widthPx, heightPx)
532                        : Math.min(widthPx, heightPx);
533                int height = (orientation != CellLayout.LANDSCAPE)
534                        ? Math.max(widthPx, heightPx)
535                        : Math.min(widthPx, heightPx);
536                int paddingTop = searchBarBounds.bottom;
537                int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
538                int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
539                        (numColumns * gapScale * cellWidthPx)));
540                int availableHeight = Math.max(0, height - paddingTop - paddingBottom
541                        - (int) (2 * numRows * cellHeightPx));
542                padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
543                        availableWidth / 2, paddingBottom + availableHeight / 2);
544            } else {
545                // Pad the top and bottom of the workspace with search/hotseat bar sizes
546                padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
547                        searchBarBounds.bottom,
548                        desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
549                        hotseatBarHeightPx + pageIndicatorHeightPx);
550            }
551        }
552        return padding;
553    }
554
555    int getWorkspacePageSpacing(int orientation) {
556        if ((orientation == CellLayout.LANDSCAPE &&
557                transposeLayoutWithOrientation) || isLargeTablet()) {
558            // In landscape mode the page spacing is set to the default.
559            return defaultPageSpacingPx;
560        } else {
561            // In portrait, we want the pages spaced such that there is no
562            // overhang of the previous / next page into the current page viewport.
563            // We assume symmetrical padding in portrait mode.
564            return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
565        }
566    }
567
568    Rect getOverviewModeButtonBarRect() {
569        int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
570        zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
571                Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
572        return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
573    }
574
575    float getOverviewModeScale() {
576        Rect workspacePadding = getWorkspacePadding();
577        Rect overviewBar = getOverviewModeButtonBarRect();
578        int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
579        return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
580    }
581
582    // The rect returned will be extended to below the system ui that covers the workspace
583    Rect getHotseatRect() {
584        if (isVerticalBarLayout()) {
585            return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
586                    Integer.MAX_VALUE, availableHeightPx);
587        } else {
588            return new Rect(0, availableHeightPx - hotseatBarHeightPx,
589                    availableWidthPx, Integer.MAX_VALUE);
590        }
591    }
592
593    int calculateCellWidth(int width, int countX) {
594        return width / countX;
595    }
596    int calculateCellHeight(int height, int countY) {
597        return height / countY;
598    }
599
600    boolean isPhone() {
601        return !isTablet && !isLargeTablet;
602    }
603    boolean isTablet() {
604        return isTablet;
605    }
606    boolean isLargeTablet() {
607        return isLargeTablet;
608    }
609
610    boolean isVerticalBarLayout() {
611        return isLandscape && transposeLayoutWithOrientation;
612    }
613
614    boolean shouldFadeAdjacentWorkspaceScreens() {
615        return isVerticalBarLayout() || isLargeTablet();
616    }
617
618    int getVisibleChildCount(ViewGroup parent) {
619        int visibleChildren = 0;
620        for (int i = 0; i < parent.getChildCount(); i++) {
621            if (parent.getChildAt(i).getVisibility() != View.GONE) {
622                visibleChildren++;
623            }
624        }
625        return visibleChildren;
626    }
627
628    int calculateOverviewModeWidth(int visibleChildCount) {
629        return visibleChildCount * overviewModeBarItemWidthPx +
630                (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
631    }
632
633    public void layout(Launcher launcher) {
634        FrameLayout.LayoutParams lp;
635        Resources res = launcher.getResources();
636        boolean hasVerticalBarLayout = isVerticalBarLayout();
637
638        // Layout the search bar space
639        View searchBar = launcher.getSearchBar();
640        lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
641        if (hasVerticalBarLayout) {
642            // Vertical search bar space
643            lp.gravity = Gravity.TOP | Gravity.LEFT;
644            lp.width = searchBarSpaceHeightPx;
645            lp.height = LayoutParams.WRAP_CONTENT;
646            searchBar.setPadding(
647                    0, 2 * edgeMarginPx, 0,
648                    2 * edgeMarginPx);
649
650            LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
651            targets.setOrientation(LinearLayout.VERTICAL);
652        } else {
653            // Horizontal search bar space
654            lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
655            lp.width = searchBarSpaceWidthPx;
656            lp.height = searchBarSpaceHeightPx;
657            searchBar.setPadding(
658                    2 * edgeMarginPx,
659                    getSearchBarTopOffset(),
660                    2 * edgeMarginPx, 0);
661        }
662        searchBar.setLayoutParams(lp);
663
664        // Layout the voice proxy
665        View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
666        if (voiceButtonProxy != null) {
667            if (hasVerticalBarLayout) {
668                // TODO: MOVE THIS INTO SEARCH BAR MEASURE
669            } else {
670                lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
671                lp.gravity = Gravity.TOP | Gravity.END;
672                lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
673                        2 * iconSizePx;
674                lp.height = searchBarSpaceHeightPx;
675            }
676        }
677
678        // Layout the workspace
679        PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
680        lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
681        lp.gravity = Gravity.CENTER;
682        int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
683        Rect padding = getWorkspacePadding(orientation);
684        workspace.setLayoutParams(lp);
685        workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
686        workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
687
688        // Layout the hotseat
689        View hotseat = launcher.findViewById(R.id.hotseat);
690        lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
691        if (hasVerticalBarLayout) {
692            // Vertical hotseat
693            lp.gravity = Gravity.END;
694            lp.width = hotseatBarHeightPx;
695            lp.height = LayoutParams.MATCH_PARENT;
696            hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
697        } else if (isTablet()) {
698            // Pad the hotseat with the workspace padding calculated above
699            lp.gravity = Gravity.BOTTOM;
700            lp.width = LayoutParams.MATCH_PARENT;
701            lp.height = hotseatBarHeightPx;
702            hotseat.setPadding(edgeMarginPx + padding.left, 0,
703                    edgeMarginPx + padding.right,
704                    2 * edgeMarginPx);
705        } else {
706            // For phones, layout the hotseat without any bottom margin
707            // to ensure that we have space for the folders
708            lp.gravity = Gravity.BOTTOM;
709            lp.width = LayoutParams.MATCH_PARENT;
710            lp.height = hotseatBarHeightPx;
711            hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
712                    2 * edgeMarginPx, 0);
713        }
714        hotseat.setLayoutParams(lp);
715
716        // Layout the page indicators
717        View pageIndicator = launcher.findViewById(R.id.page_indicator);
718        if (pageIndicator != null) {
719            if (hasVerticalBarLayout) {
720                // Hide the page indicators when we have vertical search/hotseat
721                pageIndicator.setVisibility(View.GONE);
722            } else {
723                // Put the page indicators above the hotseat
724                lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
725                lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
726                lp.width = LayoutParams.WRAP_CONTENT;
727                lp.height = LayoutParams.WRAP_CONTENT;
728                lp.bottomMargin = hotseatBarHeightPx;
729                pageIndicator.setLayoutParams(lp);
730            }
731        }
732
733        // Layout AllApps
734        AppsCustomizeTabHost host = (AppsCustomizeTabHost)
735                launcher.findViewById(R.id.apps_customize_pane);
736        if (host != null) {
737            // Center the all apps page indicator
738            int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
739                    (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
740            pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
741            if (pageIndicator != null) {
742                lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
743                lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
744                lp.width = LayoutParams.WRAP_CONTENT;
745                lp.height = pageIndicatorHeight;
746                pageIndicator.setLayoutParams(lp);
747            }
748
749            AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
750                    host.findViewById(R.id.apps_customize_pane_content);
751            padding = new Rect();
752            if (pagedView != null) {
753                // Constrain the dimensions of all apps so that it does not span the full width
754                int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
755                        (2 * (allAppsNumCols + 1));
756                int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
757                        (2 * (allAppsNumRows + 1));
758                paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
759                paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
760                int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
761                int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
762                // Only adjust the side paddings on landscape phones, or tablets
763                if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
764                    padding.left = padding.right = gridPaddingLR;
765                }
766                // The icons are centered, so we can't just offset by the page indicator height
767                // because the empty space will actually be pageIndicatorHeight + paddingTB
768                padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
769                pagedView.setAllAppsPadding(padding);
770                pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
771            }
772        }
773
774        // Layout the Overview Mode
775        ViewGroup overviewMode = launcher.getOverviewPanel();
776        if (overviewMode != null) {
777            Rect r = getOverviewModeButtonBarRect();
778            lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
779            lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
780            lp.width = Math.min(availableWidthPx,
781                    calculateOverviewModeWidth(getVisibleChildCount(overviewMode)));
782            lp.height = r.height();
783            overviewMode.setLayoutParams(lp);
784        }
785    }
786}
787