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