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     DeviceProfile profile;
     47     float widthDps;
     48     float heightDps;
     49     float value;
     50     PointF dimens;
     51 
     52     DeviceProfileQuery(DeviceProfile p, float v) {
     53         widthDps = p.minWidthDps;
     54         heightDps = p.minHeightDps;
     55         value = v;
     56         dimens = new PointF(widthDps, heightDps);
     57         profile = p;
     58     }
     59 }
     60 
     61 public class DeviceProfile {
     62     public static interface DeviceProfileCallbacks {
     63         public void onAvailableSizeChanged(DeviceProfile grid);
     64     }
     65 
     66     String name;
     67     float minWidthDps;
     68     float minHeightDps;
     69     float numRows;
     70     float numColumns;
     71     float numHotseatIcons;
     72     float iconSize;
     73     private float iconTextSize;
     74     private int iconDrawablePaddingOriginalPx;
     75     private float hotseatIconSize;
     76 
     77     int defaultLayoutId;
     78     int defaultNoAllAppsLayoutId;
     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, int dnalId) {
    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         defaultNoAllAppsLayoutId = dnalId;
    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         // Snap to the closest default no all-apps layout id
    219         defaultNoAllAppsLayoutId = closestProfile.defaultNoAllAppsLayoutId;
    220 
    221         // Interpolate the icon size
    222         points.clear();
    223         for (DeviceProfile p : profiles) {
    224             points.add(new DeviceProfileQuery(p, p.iconSize));
    225         }
    226         iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
    227 
    228         // AllApps uses the original non-scaled icon size
    229         allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
    230 
    231         // Interpolate the icon text size
    232         points.clear();
    233         for (DeviceProfile p : profiles) {
    234             points.add(new DeviceProfileQuery(p, p.iconTextSize));
    235         }
    236         iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
    237         iconDrawablePaddingOriginalPx =
    238                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
    239         // AllApps uses the original non-scaled icon text size
    240         allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
    241 
    242         // Interpolate the hotseat icon size
    243         points.clear();
    244         for (DeviceProfile p : profiles) {
    245             points.add(new DeviceProfileQuery(p, p.hotseatIconSize));
    246         }
    247         // Hotseat
    248         hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
    249 
    250         // If the partner customization apk contains any grid overrides, apply them
    251         applyPartnerDeviceProfileOverrides(context, dm);
    252 
    253         // Calculate the remaining vars
    254         updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
    255         updateAvailableDimensions(context);
    256         computeAllAppsButtonSize(context);
    257     }
    258 
    259     /**
    260      * Apply any Partner customization grid overrides.
    261      *
    262      * Currently we support: all apps row / column count.
    263      */
    264     private void applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm) {
    265         Partner p = Partner.get(ctx.getPackageManager());
    266         if (p != null) {
    267             DeviceProfile partnerDp = p.getDeviceProfileOverride(dm);
    268             if (partnerDp != null) {
    269                 if (partnerDp.numRows > 0 && partnerDp.numColumns > 0) {
    270                     numRows = partnerDp.numRows;
    271                     numColumns = partnerDp.numColumns;
    272                 }
    273                 if (partnerDp.allAppsShortEdgeCount > 0 && partnerDp.allAppsLongEdgeCount > 0) {
    274                     allAppsShortEdgeCount = partnerDp.allAppsShortEdgeCount;
    275                     allAppsLongEdgeCount = partnerDp.allAppsLongEdgeCount;
    276                 }
    277                 if (partnerDp.iconSize > 0) {
    278                     iconSize = partnerDp.iconSize;
    279                     // AllApps uses the original non-scaled icon size
    280                     allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
    281                 }
    282             }
    283         }
    284     }
    285 
    286     /**
    287      * Determine the exact visual footprint of the all apps button, taking into account scaling
    288      * and internal padding of the drawable.
    289      */
    290     private void computeAllAppsButtonSize(Context context) {
    291         Resources res = context.getResources();
    292         float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
    293         LauncherAppState app = LauncherAppState.getInstance();
    294         allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding));
    295     }
    296 
    297     void addCallback(DeviceProfileCallbacks cb) {
    298         mCallbacks.add(cb);
    299         cb.onAvailableSizeChanged(this);
    300     }
    301     void removeCallback(DeviceProfileCallbacks cb) {
    302         mCallbacks.remove(cb);
    303     }
    304 
    305     private int getDeviceOrientation(Context context) {
    306         WindowManager windowManager =  (WindowManager)
    307                 context.getSystemService(Context.WINDOW_SERVICE);
    308         Resources resources = context.getResources();
    309         DisplayMetrics dm = resources.getDisplayMetrics();
    310         Configuration config = resources.getConfiguration();
    311         int rotation = windowManager.getDefaultDisplay().getRotation();
    312 
    313         boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
    314                 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
    315         boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
    316                 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
    317         if (isLandscape || isRotatedPortrait) {
    318             return CellLayout.LANDSCAPE;
    319         } else {
    320             return CellLayout.PORTRAIT;
    321         }
    322     }
    323 
    324     private void updateAvailableDimensions(Context context) {
    325         WindowManager windowManager =  (WindowManager)
    326                 context.getSystemService(Context.WINDOW_SERVICE);
    327         Display display = windowManager.getDefaultDisplay();
    328         Resources resources = context.getResources();
    329         DisplayMetrics dm = resources.getDisplayMetrics();
    330         Configuration config = resources.getConfiguration();
    331 
    332         // There are three possible configurations that the dynamic grid accounts for, portrait,
    333         // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
    334         // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
    335         // the height is the smallest height (either with the nav bar at the bottom or to the
    336         // side) and otherwise, the height is simply the largest possible height for a portrait
    337         // device.
    338         Point size = new Point();
    339         Point smallestSize = new Point();
    340         Point largestSize = new Point();
    341         display.getSize(size);
    342         display.getCurrentSizeRange(smallestSize, largestSize);
    343         availableWidthPx = size.x;
    344         if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
    345             availableHeightPx = smallestSize.y;
    346         } else {
    347             availableHeightPx = largestSize.y;
    348         }
    349 
    350         // Check to see if the icons fit in the new available height.  If not, then we need to
    351         // shrink the icon size.
    352         float scale = 1f;
    353         int drawablePadding = iconDrawablePaddingOriginalPx;
    354         updateIconSize(1f, drawablePadding, resources, dm);
    355         float usedHeight = (cellHeightPx * numRows);
    356 
    357         Rect workspacePadding = getWorkspacePadding();
    358         int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
    359         if (usedHeight > maxHeight) {
    360             scale = maxHeight / usedHeight;
    361             drawablePadding = 0;
    362         }
    363         updateIconSize(scale, drawablePadding, resources, dm);
    364 
    365         // Make the callbacks
    366         for (DeviceProfileCallbacks cb : mCallbacks) {
    367             cb.onAvailableSizeChanged(this);
    368         }
    369     }
    370 
    371     private void updateIconSize(float scale, int drawablePadding, Resources resources,
    372                                 DisplayMetrics dm) {
    373         iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
    374         iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
    375         iconDrawablePaddingPx = drawablePadding;
    376         hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
    377 
    378         // Search Bar
    379         searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
    380         searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
    381         searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
    382         searchBarSpaceHeightPx = searchBarHeightPx + getSearchBarTopOffset();
    383 
    384         // Calculate the actual text height
    385         Paint textPaint = new Paint();
    386         textPaint.setTextSize(iconTextSizePx);
    387         FontMetrics fm = textPaint.getFontMetrics();
    388         cellWidthPx = iconSizePx;
    389         cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
    390         final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale);
    391         dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
    392 
    393         // Hotseat
    394         hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
    395         hotseatCellWidthPx = iconSizePx;
    396         hotseatCellHeightPx = iconSizePx;
    397 
    398         // Folder
    399         folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
    400         folderCellHeightPx = cellHeightPx + edgeMarginPx;
    401         folderBackgroundOffset = -edgeMarginPx;
    402         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
    403 
    404         // All Apps
    405         Rect padding = getWorkspacePadding(isLandscape ?
    406                 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
    407         int pageIndicatorOffset =
    408                 resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
    409         allAppsCellWidthPx = allAppsIconSizePx;
    410         allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
    411         int maxLongEdgeCellCount =
    412                 resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
    413         int maxShortEdgeCellCount =
    414                 resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
    415         int minEdgeCellCount =
    416                 resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
    417         int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
    418         int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
    419 
    420         if (allAppsShortEdgeCount > 0 && allAppsLongEdgeCount > 0) {
    421             allAppsNumRows = isLandscape ? allAppsShortEdgeCount : allAppsLongEdgeCount;
    422             allAppsNumCols = isLandscape ? allAppsLongEdgeCount : allAppsShortEdgeCount;
    423         } else {
    424             allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
    425                     (allAppsCellHeightPx + allAppsCellPaddingPx);
    426             allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
    427             allAppsNumCols = (availableWidthPx) /
    428                     (allAppsCellWidthPx + allAppsCellPaddingPx);
    429             allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
    430         }
    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     /** Returns the workspace padding in the specified orientation */
    592     Rect getWorkspacePadding() {
    593         return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
    594     }
    595     Rect getWorkspacePadding(int orientation) {
    596         Rect searchBarBounds = getSearchBarBounds(orientation);
    597         Rect padding = new Rect();
    598         if (orientation == CellLayout.LANDSCAPE &&
    599                 transposeLayoutWithOrientation) {
    600             // Pad the left and right of the workspace with search/hotseat bar sizes
    601             if (isLayoutRtl) {
    602                 padding.set(hotseatBarHeightPx, edgeMarginPx,
    603                         searchBarBounds.width(), edgeMarginPx);
    604             } else {
    605                 padding.set(searchBarBounds.width(), edgeMarginPx,
    606                         hotseatBarHeightPx, edgeMarginPx);
    607             }
    608         } else {
    609             if (isTablet()) {
    610                 // Pad the left and right of the workspace to ensure consistent spacing
    611                 // between all icons
    612                 float gapScale = 1f + (dragViewScale - 1f) / 2f;
    613                 int width = (orientation == CellLayout.LANDSCAPE)
    614                         ? Math.max(widthPx, heightPx)
    615                         : Math.min(widthPx, heightPx);
    616                 int height = (orientation != CellLayout.LANDSCAPE)
    617                         ? Math.max(widthPx, heightPx)
    618                         : Math.min(widthPx, heightPx);
    619                 int paddingTop = searchBarBounds.bottom;
    620                 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
    621                 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
    622                         (numColumns * gapScale * cellWidthPx)));
    623                 int availableHeight = Math.max(0, height - paddingTop - paddingBottom
    624                         - (int) (2 * numRows * cellHeightPx));
    625                 padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
    626                         availableWidth / 2, paddingBottom + availableHeight / 2);
    627             } else {
    628                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
    629                 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
    630                         searchBarBounds.bottom,
    631                         desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
    632                         hotseatBarHeightPx + pageIndicatorHeightPx);
    633             }
    634         }
    635         return padding;
    636     }
    637 
    638     int getWorkspacePageSpacing(int orientation) {
    639         if ((orientation == CellLayout.LANDSCAPE &&
    640                 transposeLayoutWithOrientation) || isLargeTablet()) {
    641             // In landscape mode the page spacing is set to the default.
    642             return defaultPageSpacingPx;
    643         } else {
    644             // In portrait, we want the pages spaced such that there is no
    645             // overhang of the previous / next page into the current page viewport.
    646             // We assume symmetrical padding in portrait mode.
    647             return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
    648         }
    649     }
    650 
    651     Rect getOverviewModeButtonBarRect() {
    652         int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
    653         zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
    654                 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
    655         return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
    656     }
    657 
    658     float getOverviewModeScale() {
    659         Rect workspacePadding = getWorkspacePadding();
    660         Rect overviewBar = getOverviewModeButtonBarRect();
    661         int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
    662         return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
    663     }
    664 
    665     // The rect returned will be extended to below the system ui that covers the workspace
    666     Rect getHotseatRect() {
    667         if (isVerticalBarLayout()) {
    668             return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
    669                     Integer.MAX_VALUE, availableHeightPx);
    670         } else {
    671             return new Rect(0, availableHeightPx - hotseatBarHeightPx,
    672                     availableWidthPx, Integer.MAX_VALUE);
    673         }
    674     }
    675 
    676     int calculateCellWidth(int width, int countX) {
    677         return width / countX;
    678     }
    679     int calculateCellHeight(int height, int countY) {
    680         return height / countY;
    681     }
    682 
    683     boolean isPhone() {
    684         return !isTablet && !isLargeTablet;
    685     }
    686     boolean isTablet() {
    687         return isTablet;
    688     }
    689     boolean isLargeTablet() {
    690         return isLargeTablet;
    691     }
    692 
    693     boolean isVerticalBarLayout() {
    694         return isLandscape && transposeLayoutWithOrientation;
    695     }
    696 
    697     boolean shouldFadeAdjacentWorkspaceScreens() {
    698         return isVerticalBarLayout() || isLargeTablet();
    699     }
    700 
    701     int getVisibleChildCount(ViewGroup parent) {
    702         int visibleChildren = 0;
    703         for (int i = 0; i < parent.getChildCount(); i++) {
    704             if (parent.getChildAt(i).getVisibility() != View.GONE) {
    705                 visibleChildren++;
    706             }
    707         }
    708         return visibleChildren;
    709     }
    710 
    711     int calculateOverviewModeWidth(int visibleChildCount) {
    712         return visibleChildCount * overviewModeBarItemWidthPx +
    713                 (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
    714     }
    715 
    716     public void layout(Launcher launcher) {
    717         FrameLayout.LayoutParams lp;
    718         Resources res = launcher.getResources();
    719         boolean hasVerticalBarLayout = isVerticalBarLayout();
    720 
    721         // Layout the search bar space
    722         View searchBar = launcher.getSearchBar();
    723         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
    724         if (hasVerticalBarLayout) {
    725             // Vertical search bar space
    726             lp.gravity = Gravity.TOP | Gravity.LEFT;
    727             lp.width = searchBarSpaceHeightPx;
    728             lp.height = LayoutParams.WRAP_CONTENT;
    729             searchBar.setPadding(
    730                     0, 2 * edgeMarginPx, 0,
    731                     2 * edgeMarginPx);
    732 
    733             LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
    734             targets.setOrientation(LinearLayout.VERTICAL);
    735         } else {
    736             // Horizontal search bar space
    737             lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
    738             lp.width = searchBarSpaceWidthPx;
    739             lp.height = searchBarSpaceHeightPx;
    740             searchBar.setPadding(
    741                     2 * edgeMarginPx,
    742                     getSearchBarTopOffset(),
    743                     2 * edgeMarginPx, 0);
    744         }
    745         searchBar.setLayoutParams(lp);
    746 
    747         // Layout the voice proxy
    748         View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
    749         if (voiceButtonProxy != null) {
    750             if (hasVerticalBarLayout) {
    751                 // TODO: MOVE THIS INTO SEARCH BAR MEASURE
    752             } else {
    753                 lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
    754                 lp.gravity = Gravity.TOP | Gravity.END;
    755                 lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
    756                         2 * iconSizePx;
    757                 lp.height = searchBarSpaceHeightPx;
    758             }
    759         }
    760 
    761         // Layout the workspace
    762         PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
    763         lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
    764         lp.gravity = Gravity.CENTER;
    765         int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
    766         Rect padding = getWorkspacePadding(orientation);
    767         workspace.setLayoutParams(lp);
    768         workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
    769         workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
    770 
    771         // Layout the hotseat
    772         View hotseat = launcher.findViewById(R.id.hotseat);
    773         lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
    774         if (hasVerticalBarLayout) {
    775             // Vertical hotseat
    776             lp.gravity = Gravity.END;
    777             lp.width = hotseatBarHeightPx;
    778             lp.height = LayoutParams.MATCH_PARENT;
    779             hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
    780         } else if (isTablet()) {
    781             // Pad the hotseat with the workspace padding calculated above
    782             lp.gravity = Gravity.BOTTOM;
    783             lp.width = LayoutParams.MATCH_PARENT;
    784             lp.height = hotseatBarHeightPx;
    785             hotseat.setPadding(edgeMarginPx + padding.left, 0,
    786                     edgeMarginPx + padding.right,
    787                     2 * edgeMarginPx);
    788         } else {
    789             // For phones, layout the hotseat without any bottom margin
    790             // to ensure that we have space for the folders
    791             lp.gravity = Gravity.BOTTOM;
    792             lp.width = LayoutParams.MATCH_PARENT;
    793             lp.height = hotseatBarHeightPx;
    794             hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
    795                     2 * edgeMarginPx, 0);
    796         }
    797         hotseat.setLayoutParams(lp);
    798 
    799         // Layout the page indicators
    800         View pageIndicator = launcher.findViewById(R.id.page_indicator);
    801         if (pageIndicator != null) {
    802             if (hasVerticalBarLayout) {
    803                 // Hide the page indicators when we have vertical search/hotseat
    804                 pageIndicator.setVisibility(View.GONE);
    805             } else {
    806                 // Put the page indicators above the hotseat
    807                 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
    808                 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
    809                 lp.width = LayoutParams.WRAP_CONTENT;
    810                 lp.height = LayoutParams.WRAP_CONTENT;
    811                 lp.bottomMargin = hotseatBarHeightPx;
    812                 pageIndicator.setLayoutParams(lp);
    813             }
    814         }
    815 
    816         // Layout AllApps
    817         AppsCustomizeTabHost host = (AppsCustomizeTabHost)
    818                 launcher.findViewById(R.id.apps_customize_pane);
    819         if (host != null) {
    820             // Center the all apps page indicator
    821             int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
    822                     (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
    823             pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
    824             if (pageIndicator != null) {
    825                 LinearLayout.LayoutParams lllp = (LinearLayout.LayoutParams) pageIndicator.getLayoutParams();
    826                 lllp.width = LayoutParams.WRAP_CONTENT;
    827                 lllp.height = pageIndicatorHeight;
    828                 pageIndicator.setLayoutParams(lllp);
    829             }
    830 
    831             AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
    832                     host.findViewById(R.id.apps_customize_pane_content);
    833 
    834             FrameLayout fakePageContainer = (FrameLayout)
    835                     host.findViewById(R.id.fake_page_container);
    836             FrameLayout fakePage = (FrameLayout) host.findViewById(R.id.fake_page);
    837 
    838             padding = new Rect();
    839             if (pagedView != null) {
    840                 // Constrain the dimensions of all apps so that it does not span the full width
    841                 int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
    842                         (2 * (allAppsNumCols + 1));
    843                 int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
    844                         (2 * (allAppsNumRows + 1));
    845                 paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
    846                 paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
    847                 int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
    848                 int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
    849                 // Only adjust the side paddings on landscape phones, or tablets
    850                 if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
    851                     padding.left = padding.right = gridPaddingLR;
    852                 }
    853 
    854                 // The icons are centered, so we can't just offset by the page indicator height
    855                 // because the empty space will actually be pageIndicatorHeight + paddingTB
    856                 padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
    857 
    858                 pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
    859                 fakePage.setBackground(res.getDrawable(R.drawable.quantum_panel));
    860 
    861                 // Horizontal padding for the whole paged view
    862                 int pagedFixedViewPadding =
    863                         res.getDimensionPixelSize(R.dimen.apps_customize_horizontal_padding);
    864 
    865                 padding.left += pagedFixedViewPadding;
    866                 padding.right += pagedFixedViewPadding;
    867 
    868                 pagedView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
    869                 fakePageContainer.setPadding(padding.left, padding.top, padding.right, padding.bottom);
    870 
    871             }
    872         }
    873 
    874         // Layout the Overview Mode
    875         ViewGroup overviewMode = launcher.getOverviewPanel();
    876         if (overviewMode != null) {
    877             Rect r = getOverviewModeButtonBarRect();
    878             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
    879             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
    880             lp.width = Math.min(availableWidthPx,
    881                     calculateOverviewModeWidth(getVisibleChildCount(overviewMode)));
    882             lp.height = r.height();
    883             overviewMode.setLayoutParams(lp);
    884         }
    885     }
    886 }
    887