Home | History | Annotate | Download | only in launcher3
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.launcher3;
     18 
     19 import android.appwidget.AppWidgetHostView;
     20 import android.content.ComponentName;
     21 import android.content.Context;
     22 import android.content.res.Configuration;
     23 import android.content.res.Resources;
     24 import android.graphics.Paint;
     25 import android.graphics.Paint.FontMetrics;
     26 import android.graphics.Point;
     27 import android.graphics.PointF;
     28 import android.graphics.Rect;
     29 import android.util.DisplayMetrics;
     30 import android.view.Display;
     31 import android.view.Gravity;
     32 import android.view.Surface;
     33 import android.view.View;
     34 import android.view.ViewGroup;
     35 import android.view.ViewGroup.LayoutParams;
     36 import android.view.WindowManager;
     37 import android.widget.FrameLayout;
     38 import android.widget.LinearLayout;
     39 
     40 import java.util.ArrayList;
     41 import java.util.Collections;
     42 import java.util.Comparator;
     43 
     44 
     45 class 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 
     59 public 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