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