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.Resources;
     23 import android.graphics.Point;
     24 import android.graphics.PointF;
     25 import android.graphics.Rect;
     26 import android.util.DisplayMetrics;
     27 import android.view.Gravity;
     28 import android.view.View;
     29 import android.view.ViewGroup;
     30 import android.view.ViewGroup.LayoutParams;
     31 import android.widget.FrameLayout;
     32 
     33 import com.android.launcher3.CellLayout.ContainerType;
     34 import com.android.launcher3.badge.BadgeRenderer;
     35 import com.android.launcher3.config.FeatureFlags;
     36 
     37 import java.util.ArrayList;
     38 
     39 public class DeviceProfile {
     40 
     41     public interface LauncherLayoutChangeListener {
     42         void onLauncherLayoutChanged();
     43     }
     44 
     45     public final InvariantDeviceProfile inv;
     46 
     47     // Device properties
     48     public final boolean isTablet;
     49     public final boolean isLargeTablet;
     50     public final boolean isPhone;
     51     public final boolean transposeLayoutWithOrientation;
     52 
     53     // Device properties in current orientation
     54     public final boolean isLandscape;
     55     public final int widthPx;
     56     public final int heightPx;
     57     public final int availableWidthPx;
     58     public final int availableHeightPx;
     59     /**
     60      * The maximum amount of left/right workspace padding as a percentage of the screen width.
     61      * To be clear, this means that up to 7% of the screen width can be used as left padding, and
     62      * 7% of the screen width can be used as right padding.
     63      */
     64     private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f;
     65 
     66     // Overview mode
     67     private final int overviewModeMinIconZoneHeightPx;
     68     private final int overviewModeMaxIconZoneHeightPx;
     69     private final int overviewModeBarItemWidthPx;
     70     private final int overviewModeBarSpacerWidthPx;
     71     private final float overviewModeIconZoneRatio;
     72 
     73     // Workspace
     74     private int desiredWorkspaceLeftRightMarginPx;
     75     public final int edgeMarginPx;
     76     public final Rect defaultWidgetPadding;
     77     private final int defaultPageSpacingPx;
     78     private final int topWorkspacePadding;
     79     public float workspaceSpringLoadShrinkFactor;
     80     public final int workspaceSpringLoadedBottomSpace;
     81 
     82     // Page indicator
     83     private final int pageIndicatorHeightPx;
     84     private final int pageIndicatorLandGutterLeftNavBarPx;
     85     private final int pageIndicatorLandGutterRightNavBarPx;
     86     private final int pageIndicatorLandWorkspaceOffsetPx;
     87 
     88     // Workspace icons
     89     public int iconSizePx;
     90     public int iconTextSizePx;
     91     public int iconDrawablePaddingPx;
     92     public int iconDrawablePaddingOriginalPx;
     93 
     94     public int cellWidthPx;
     95     public int cellHeightPx;
     96 
     97     // Folder
     98     public int folderBackgroundOffset;
     99     public int folderIconSizePx;
    100     public int folderIconPreviewPadding;
    101 
    102     // Folder cell
    103     public int folderCellWidthPx;
    104     public int folderCellHeightPx;
    105 
    106     // Folder child
    107     public int folderChildIconSizePx;
    108     public int folderChildTextSizePx;
    109     public int folderChildDrawablePaddingPx;
    110 
    111     // Hotseat
    112     public int hotseatCellWidthPx;
    113     public int hotseatCellHeightPx;
    114     public int hotseatIconSizePx;
    115     public int hotseatBarHeightPx;
    116     private int hotseatBarTopPaddingPx;
    117     private int hotseatBarBottomPaddingPx;
    118     private int hotseatLandGutterPx;
    119 
    120     // All apps
    121     public int allAppsNumCols;
    122     public int allAppsNumPredictiveCols;
    123     public int allAppsButtonVisualSize;
    124     public int allAppsIconSizePx;
    125     public int allAppsIconDrawablePaddingPx;
    126     public float allAppsIconTextSizePx;
    127 
    128     // Widgets
    129     public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
    130 
    131     // Drop Target
    132     public int dropTargetBarSizePx;
    133 
    134     // Insets
    135     private Rect mInsets = new Rect();
    136 
    137     // Listeners
    138     private ArrayList<LauncherLayoutChangeListener> mListeners = new ArrayList<>();
    139 
    140     // Icon badges
    141     public BadgeRenderer mBadgeRenderer;
    142 
    143     public DeviceProfile(Context context, InvariantDeviceProfile inv,
    144             Point minSize, Point maxSize,
    145             int width, int height, boolean isLandscape) {
    146 
    147         this.inv = inv;
    148         this.isLandscape = isLandscape;
    149 
    150         Resources res = context.getResources();
    151         DisplayMetrics dm = res.getDisplayMetrics();
    152 
    153         // Constants from resources
    154         isTablet = res.getBoolean(R.bool.is_tablet);
    155         isLargeTablet = res.getBoolean(R.bool.is_large_tablet);
    156         isPhone = !isTablet && !isLargeTablet;
    157 
    158         // Some more constants
    159         transposeLayoutWithOrientation =
    160                 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
    161 
    162         ComponentName cn = new ComponentName(context.getPackageName(),
    163                 this.getClass().getName());
    164         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
    165         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
    166         desiredWorkspaceLeftRightMarginPx = edgeMarginPx;
    167         pageIndicatorHeightPx =
    168                 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
    169         pageIndicatorLandGutterLeftNavBarPx = res.getDimensionPixelSize(
    170                 R.dimen.dynamic_grid_page_indicator_gutter_width_left_nav_bar);
    171         pageIndicatorLandWorkspaceOffsetPx =
    172                 res.getDimensionPixelSize(R.dimen.all_apps_caret_workspace_offset);
    173         pageIndicatorLandGutterRightNavBarPx = res.getDimensionPixelSize(
    174                 R.dimen.dynamic_grid_page_indicator_gutter_width_right_nav_bar);
    175         defaultPageSpacingPx =
    176                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
    177         topWorkspacePadding =
    178                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_top_padding);
    179         overviewModeMinIconZoneHeightPx =
    180                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
    181         overviewModeMaxIconZoneHeightPx =
    182                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
    183         overviewModeBarItemWidthPx =
    184                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
    185         overviewModeBarSpacerWidthPx =
    186                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
    187         overviewModeIconZoneRatio =
    188                 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
    189         iconDrawablePaddingOriginalPx =
    190                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
    191         dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
    192         workspaceSpringLoadedBottomSpace =
    193                 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
    194         hotseatBarHeightPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_height);
    195         hotseatBarTopPaddingPx =
    196                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
    197         hotseatBarBottomPaddingPx = 0;
    198         hotseatLandGutterPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_gutter_width);
    199 
    200         // Determine sizes.
    201         widthPx = width;
    202         heightPx = height;
    203         if (isLandscape) {
    204             availableWidthPx = maxSize.x;
    205             availableHeightPx = minSize.y;
    206         } else {
    207             availableWidthPx = minSize.x;
    208             availableHeightPx = maxSize.y;
    209         }
    210 
    211         // Calculate the remaining vars
    212         updateAvailableDimensions(dm, res);
    213         computeAllAppsButtonSize(context);
    214 
    215         // This is done last, after iconSizePx is calculated above.
    216         mBadgeRenderer = new BadgeRenderer(context, iconSizePx);
    217     }
    218 
    219     DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
    220         // In multi-window mode, we can have widthPx = availableWidthPx
    221         // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
    222         // widthPx and heightPx values where it's needed.
    223         DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
    224                 isLandscape);
    225 
    226         // Hide labels on the workspace.
    227         profile.iconTextSizePx = 0;
    228         profile.cellHeightPx = profile.iconSizePx + profile.iconDrawablePaddingPx
    229                 + Utilities.calculateTextHeight(profile.iconTextSizePx);
    230 
    231         // The nav bar is black so we add bottom padding to visually center hotseat icons.
    232         profile.hotseatBarBottomPaddingPx = profile.hotseatBarTopPaddingPx;
    233 
    234         // We use these scales to measure and layout the widgets using their full invariant profile
    235         // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
    236         float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
    237         float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
    238         profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
    239 
    240         return profile;
    241     }
    242 
    243     public void addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
    244         if (!mListeners.contains(listener)) {
    245             mListeners.add(listener);
    246         }
    247     }
    248 
    249     public void removeLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
    250         if (mListeners.contains(listener)) {
    251             mListeners.remove(listener);
    252         }
    253     }
    254 
    255     /**
    256      * Determine the exact visual footprint of the all apps button, taking into account scaling
    257      * and internal padding of the drawable.
    258      */
    259     private void computeAllAppsButtonSize(Context context) {
    260         Resources res = context.getResources();
    261         float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
    262         allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding)) - context.getResources()
    263                         .getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
    264     }
    265 
    266     private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
    267         updateIconSize(1f, iconDrawablePaddingOriginalPx, res, dm);
    268 
    269         // Check to see if the icons fit within the available height.  If not, then scale down.
    270         float usedHeight = (cellHeightPx * inv.numRows);
    271         int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
    272         if (usedHeight > maxHeight) {
    273             float scale = maxHeight / usedHeight;
    274             updateIconSize(scale, 0, res, dm);
    275         }
    276 
    277         updateAvailableFolderCellDimensions(dm, res);
    278     }
    279 
    280     private void updateIconSize(float scale, int drawablePadding, Resources res,
    281                                 DisplayMetrics dm) {
    282         iconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale);
    283         iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
    284         iconDrawablePaddingPx = drawablePadding;
    285         hotseatIconSizePx = (int) (Utilities.pxFromDp(inv.hotseatIconSize, dm) * scale);
    286         allAppsIconSizePx = iconSizePx;
    287         allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
    288         allAppsIconTextSizePx = iconTextSizePx;
    289 
    290         cellWidthPx = iconSizePx;
    291         cellHeightPx = iconSizePx + iconDrawablePaddingPx
    292                 + Utilities.calculateTextHeight(iconTextSizePx);
    293 
    294         // Hotseat
    295         hotseatCellWidthPx = iconSizePx;
    296         hotseatCellHeightPx = iconSizePx;
    297 
    298         if (!isVerticalBarLayout()) {
    299             int expectedWorkspaceHeight = availableHeightPx - hotseatBarHeightPx
    300                     - pageIndicatorHeightPx - topWorkspacePadding;
    301             float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
    302             workspaceSpringLoadShrinkFactor = Math.min(
    303                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f,
    304                     1 - (minRequiredHeight / expectedWorkspaceHeight));
    305         } else {
    306             workspaceSpringLoadShrinkFactor =
    307                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
    308         }
    309 
    310         // Folder icon
    311         folderBackgroundOffset = -edgeMarginPx;
    312         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
    313         folderIconPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
    314     }
    315 
    316     private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) {
    317         int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
    318                 + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
    319                 + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
    320 
    321         updateFolderCellSize(1f, dm, res);
    322 
    323         // Don't let the folder get too close to the edges of the screen.
    324         int folderMargin = 4 * edgeMarginPx;
    325 
    326         // Check if the icons fit within the available height.
    327         float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize;
    328         int maxHeight = availableHeightPx - getTotalWorkspacePadding().y - folderMargin;
    329         float scaleY = maxHeight / usedHeight;
    330 
    331         // Check if the icons fit within the available width.
    332         float usedWidth = folderCellWidthPx * inv.numFolderColumns;
    333         int maxWidth = availableWidthPx - getTotalWorkspacePadding().x - folderMargin;
    334         float scaleX = maxWidth / usedWidth;
    335 
    336         float scale = Math.min(scaleX, scaleY);
    337         if (scale < 1f) {
    338             updateFolderCellSize(scale, dm, res);
    339         }
    340     }
    341 
    342     private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) {
    343         folderChildIconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale);
    344         folderChildTextSizePx =
    345                 (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
    346 
    347         int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
    348         int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale);
    349         int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale);
    350 
    351         folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
    352         folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
    353         folderChildDrawablePaddingPx = Math.max(0,
    354                 (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
    355     }
    356 
    357     public void updateInsets(Rect insets) {
    358         mInsets.set(insets);
    359     }
    360 
    361     public void updateAppsViewNumCols() {
    362         allAppsNumCols = allAppsNumPredictiveCols = inv.numColumns;
    363     }
    364 
    365     /** Returns the width and height of the search bar, ignoring any padding. */
    366     public Point getSearchBarDimensForWidgetOpts() {
    367         if (isVerticalBarLayout()) {
    368             return new Point(dropTargetBarSizePx, availableHeightPx - 2 * edgeMarginPx);
    369         } else {
    370             int gap;
    371             if (isTablet) {
    372                 // Pad the left and right of the workspace to ensure consistent spacing
    373                 // between all icons
    374                 int width = getCurrentWidth();
    375                 // XXX: If the icon size changes across orientations, we will have to take
    376                 //      that into account here too.
    377                 gap = ((width - 2 * edgeMarginPx
    378                         - (inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1)))
    379                         + edgeMarginPx;
    380             } else {
    381                 gap = desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right;
    382             }
    383             return new Point(availableWidthPx - 2 * gap, dropTargetBarSizePx);
    384         }
    385     }
    386 
    387     public Point getCellSize() {
    388         Point result = new Point();
    389         // Since we are only concerned with the overall padding, layout direction does
    390         // not matter.
    391         Point padding = getTotalWorkspacePadding();
    392         result.x = calculateCellWidth(availableWidthPx - padding.x, inv.numColumns);
    393         result.y = calculateCellHeight(availableHeightPx - padding.y, inv.numRows);
    394         return result;
    395     }
    396 
    397     public Point getTotalWorkspacePadding() {
    398         Rect padding = getWorkspacePadding(null);
    399         return new Point(padding.left + padding.right, padding.top + padding.bottom);
    400     }
    401 
    402     /**
    403      * Returns the workspace padding in the specified orientation.
    404      * Note that it assumes that while in verticalBarLayout, the nav bar is on the right, as such
    405      * this value is not reliable.
    406      * Use {@link #getTotalWorkspacePadding()} instead.
    407      */
    408     public Rect getWorkspacePadding(Rect recycle) {
    409         Rect padding = recycle == null ? new Rect() : recycle;
    410         if (isVerticalBarLayout()) {
    411             if (mInsets.left > 0) {
    412                 padding.set(mInsets.left + pageIndicatorLandGutterLeftNavBarPx, 0,
    413                         hotseatBarHeightPx + hotseatLandGutterPx - mInsets.left, 2 * edgeMarginPx);
    414             } else {
    415                 padding.set(pageIndicatorLandGutterRightNavBarPx, 0,
    416                         hotseatBarHeightPx + hotseatLandGutterPx, 2 * edgeMarginPx);
    417             }
    418         } else {
    419             int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
    420             if (isTablet) {
    421                 // Pad the left and right of the workspace to ensure consistent spacing
    422                 // between all icons
    423                 int width = getCurrentWidth();
    424                 int height = getCurrentHeight();
    425                 // The amount of screen space available for left/right padding.
    426                 int availablePaddingX = Math.max(0, width - ((inv.numColumns * cellWidthPx) +
    427                         ((inv.numColumns - 1) * cellWidthPx)));
    428                 availablePaddingX = (int) Math.min(availablePaddingX,
    429                             width * MAX_HORIZONTAL_PADDING_PERCENT);
    430                 int availablePaddingY = Math.max(0, height - topWorkspacePadding - paddingBottom
    431                         - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
    432                         - hotseatBarBottomPaddingPx);
    433                 padding.set(availablePaddingX / 2, topWorkspacePadding + availablePaddingY / 2,
    434                         availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
    435             } else {
    436                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
    437                 padding.set(desiredWorkspaceLeftRightMarginPx,
    438                         topWorkspacePadding,
    439                         desiredWorkspaceLeftRightMarginPx,
    440                         paddingBottom);
    441             }
    442         }
    443         return padding;
    444     }
    445 
    446     /**
    447      * @return the bounds for which the open folders should be contained within
    448      */
    449     public Rect getAbsoluteOpenFolderBounds() {
    450         if (isVerticalBarLayout()) {
    451             // Folders should only appear right of the drop target bar and left of the hotseat
    452             return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx,
    453                     mInsets.top,
    454                     mInsets.left + availableWidthPx - hotseatBarHeightPx - edgeMarginPx,
    455                     mInsets.top + availableHeightPx);
    456         } else {
    457             // Folders should only appear below the drop target bar and above the hotseat
    458             return new Rect(mInsets.left,
    459                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
    460                     mInsets.left + availableWidthPx,
    461                     mInsets.top + availableHeightPx - hotseatBarHeightPx - pageIndicatorHeightPx -
    462                             edgeMarginPx);
    463         }
    464     }
    465 
    466     private int getWorkspacePageSpacing() {
    467         if (isVerticalBarLayout() || isLargeTablet) {
    468             // In landscape mode the page spacing is set to the default.
    469             return defaultPageSpacingPx;
    470         } else {
    471             // In portrait, we want the pages spaced such that there is no
    472             // overhang of the previous / next page into the current page viewport.
    473             // We assume symmetrical padding in portrait mode.
    474             return Math.max(defaultPageSpacingPx, getWorkspacePadding(null).left + 1);
    475         }
    476     }
    477 
    478     int getOverviewModeButtonBarHeight() {
    479         int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
    480         zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
    481                 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
    482         return zoneHeight;
    483     }
    484 
    485     public static int calculateCellWidth(int width, int countX) {
    486         return width / countX;
    487     }
    488     public static int calculateCellHeight(int height, int countY) {
    489         return height / countY;
    490     }
    491 
    492     /**
    493      * When {@code true}, the device is in landscape mode and the hotseat is on the right column.
    494      * When {@code false}, either device is in portrait mode or the device is in landscape mode and
    495      * the hotseat is on the bottom row.
    496      */
    497     public boolean isVerticalBarLayout() {
    498         return isLandscape && transposeLayoutWithOrientation;
    499     }
    500 
    501     boolean shouldFadeAdjacentWorkspaceScreens() {
    502         return isVerticalBarLayout() || isLargeTablet;
    503     }
    504 
    505     private int getVisibleChildCount(ViewGroup parent) {
    506         int visibleChildren = 0;
    507         for (int i = 0; i < parent.getChildCount(); i++) {
    508             if (parent.getChildAt(i).getVisibility() != View.GONE) {
    509                 visibleChildren++;
    510             }
    511         }
    512         return visibleChildren;
    513     }
    514 
    515     public void layout(Launcher launcher, boolean notifyListeners) {
    516         FrameLayout.LayoutParams lp;
    517         boolean hasVerticalBarLayout = isVerticalBarLayout();
    518 
    519         // Layout the search bar space
    520         Point searchBarBounds = getSearchBarDimensForWidgetOpts();
    521         View searchBar = launcher.getDropTargetBar();
    522         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
    523         lp.width = searchBarBounds.x;
    524         lp.height = searchBarBounds.y;
    525         lp.topMargin = mInsets.top + edgeMarginPx;
    526         searchBar.setLayoutParams(lp);
    527 
    528         // Layout the workspace
    529         PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
    530         Rect workspacePadding = getWorkspacePadding(null);
    531         workspace.setPadding(workspacePadding.left, workspacePadding.top, workspacePadding.right,
    532                 workspacePadding.bottom);
    533         workspace.setPageSpacing(getWorkspacePageSpacing());
    534 
    535         // Only display when enabled
    536         if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
    537             View qsbContainer = launcher.getQsbContainer();
    538             lp = (FrameLayout.LayoutParams) qsbContainer.getLayoutParams();
    539             lp.topMargin = mInsets.top + workspacePadding.top;
    540             qsbContainer.setLayoutParams(lp);
    541         }
    542 
    543         // Layout the hotseat
    544         Hotseat hotseat = (Hotseat) launcher.findViewById(R.id.hotseat);
    545         lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
    546         // We want the edges of the hotseat to line up with the edges of the workspace, but the
    547         // icons in the hotseat are a different size, and so don't line up perfectly. To account for
    548         // this, we pad the left and right of the hotseat with half of the difference of a workspace
    549         // cell vs a hotseat cell.
    550         float workspaceCellWidth = (float) getCurrentWidth() / inv.numColumns;
    551         float hotseatCellWidth = (float) getCurrentWidth() / inv.numHotseatIcons;
    552         int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
    553         if (hasVerticalBarLayout) {
    554             // Vertical hotseat -- The hotseat is fixed in the layout to be on the right of the
    555             //                     screen regardless of RTL
    556             lp.gravity = Gravity.RIGHT;
    557             lp.width = hotseatBarHeightPx + mInsets.left + mInsets.right;
    558             lp.height = LayoutParams.MATCH_PARENT;
    559             hotseat.getLayout().setPadding(mInsets.left, mInsets.top, mInsets.right,
    560                     workspacePadding.bottom);
    561         } else if (isTablet) {
    562             // Pad the hotseat with the workspace padding calculated above
    563             lp.gravity = Gravity.BOTTOM;
    564             lp.width = LayoutParams.MATCH_PARENT;
    565             lp.height = hotseatBarHeightPx + mInsets.bottom;
    566             hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left,
    567                     hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right,
    568                     hotseatBarBottomPaddingPx + mInsets.bottom);
    569         } else {
    570             // For phones, layout the hotseat without any bottom margin
    571             // to ensure that we have space for the folders
    572             lp.gravity = Gravity.BOTTOM;
    573             lp.width = LayoutParams.MATCH_PARENT;
    574             lp.height = hotseatBarHeightPx + mInsets.bottom;
    575             hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left,
    576                     hotseatBarTopPaddingPx, hotseatAdjustment + workspacePadding.right,
    577                     hotseatBarBottomPaddingPx + mInsets.bottom);
    578         }
    579         hotseat.setLayoutParams(lp);
    580 
    581         // Layout the page indicators
    582         View pageIndicator = launcher.findViewById(R.id.page_indicator);
    583         if (pageIndicator != null) {
    584             lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
    585             if (isVerticalBarLayout()) {
    586                 if (mInsets.left > 0) {
    587                     lp.leftMargin = mInsets.left + pageIndicatorLandGutterLeftNavBarPx -
    588                             lp.width - pageIndicatorLandWorkspaceOffsetPx;
    589                 } else if (mInsets.right > 0) {
    590                     lp.leftMargin = pageIndicatorLandGutterRightNavBarPx - lp.width -
    591                             pageIndicatorLandWorkspaceOffsetPx;
    592                 }
    593                 lp.bottomMargin = workspacePadding.bottom;
    594             } else {
    595                 // Put the page indicators above the hotseat
    596                 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
    597                 lp.height = pageIndicatorHeightPx;
    598                 lp.bottomMargin = hotseatBarHeightPx + mInsets.bottom;
    599             }
    600             pageIndicator.setLayoutParams(lp);
    601         }
    602 
    603         // Layout the Overview Mode
    604         ViewGroup overviewMode = launcher.getOverviewPanel();
    605         if (overviewMode != null) {
    606             int visibleChildCount = getVisibleChildCount(overviewMode);
    607             int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
    608             int maxWidth = totalItemWidth + (visibleChildCount - 1) * overviewModeBarSpacerWidthPx;
    609 
    610             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
    611             lp.width = Math.min(availableWidthPx, maxWidth);
    612             lp.height = getOverviewModeButtonBarHeight() + mInsets.bottom;
    613             overviewMode.setLayoutParams(lp);
    614         }
    615 
    616         if (notifyListeners) {
    617             for (int i = mListeners.size() - 1; i >= 0; i--) {
    618                 mListeners.get(i).onLauncherLayoutChanged();
    619             }
    620         }
    621     }
    622 
    623     private int getCurrentWidth() {
    624         return isLandscape
    625                 ? Math.max(widthPx, heightPx)
    626                 : Math.min(widthPx, heightPx);
    627     }
    628 
    629     private int getCurrentHeight() {
    630         return isLandscape
    631                 ? Math.min(widthPx, heightPx)
    632                 : Math.max(widthPx, heightPx);
    633     }
    634 
    635     public int getCellHeight(@ContainerType int containerType) {
    636         switch (containerType) {
    637             case CellLayout.WORKSPACE:
    638                 return cellHeightPx;
    639             case CellLayout.FOLDER:
    640                 return folderCellHeightPx;
    641             case CellLayout.HOTSEAT:
    642                 return hotseatCellHeightPx;
    643             default:
    644                 // ??
    645                 return 0;
    646         }
    647     }
    648 
    649     /**
    650      * @return the left/right paddings for all containers.
    651      */
    652     public final int[] getContainerPadding() {
    653         // No paddings for portrait phone
    654         if (isPhone && !isVerticalBarLayout()) {
    655             return new int[] {0, 0};
    656         }
    657 
    658         // In landscape, we match the width of the workspace
    659         int padding = (pageIndicatorLandGutterRightNavBarPx +
    660                 hotseatBarHeightPx + hotseatLandGutterPx + mInsets.left) / 2;
    661         return new int[]{ padding, padding };
    662     }
    663 
    664     public boolean shouldIgnoreLongPressToOverview(float touchX) {
    665         boolean inMultiWindowMode = this != inv.landscapeProfile && this != inv.portraitProfile;
    666         boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeMarginPx;
    667         boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeMarginPx);
    668         return !inMultiWindowMode && (touchedLhsEdge || touchedRhsEdge);
    669     }
    670 }
    671