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