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.Point;
     25 import android.graphics.PointF;
     26 import android.graphics.Rect;
     27 import android.util.DisplayMetrics;
     28 import android.view.Gravity;
     29 import android.view.View;
     30 import android.view.ViewGroup;
     31 import android.view.ViewGroup.LayoutParams;
     32 import android.widget.FrameLayout;
     33 
     34 import com.android.launcher3.CellLayout.ContainerType;
     35 import com.android.launcher3.badge.BadgeRenderer;
     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     private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
     67 
     68     // Overview mode
     69     private final int overviewModeMinIconZoneHeightPx;
     70     private final int overviewModeMaxIconZoneHeightPx;
     71     private final int overviewModeBarItemWidthPx;
     72     private final int overviewModeBarSpacerWidthPx;
     73     private final float overviewModeIconZoneRatio;
     74 
     75     // Workspace
     76     private final int desiredWorkspaceLeftRightMarginPx;
     77     public final int cellLayoutPaddingLeftRightPx;
     78     public final int cellLayoutBottomPaddingPx;
     79     public final int edgeMarginPx;
     80     public final Rect defaultWidgetPadding;
     81     private final int defaultPageSpacingPx;
     82     private final int topWorkspacePadding;
     83     public float workspaceSpringLoadShrinkFactor;
     84     public final int workspaceSpringLoadedBottomSpace;
     85 
     86     // Page indicator
     87     private int pageIndicatorSizePx;
     88     private final int pageIndicatorLandLeftNavBarGutterPx;
     89     private final int pageIndicatorLandRightNavBarGutterPx;
     90     private final int pageIndicatorLandWorkspaceOffsetPx;
     91 
     92     // Workspace icons
     93     public int iconSizePx;
     94     public int iconTextSizePx;
     95     public int iconDrawablePaddingPx;
     96     public int iconDrawablePaddingOriginalPx;
     97 
     98     public int cellWidthPx;
     99     public int cellHeightPx;
    100     public int workspaceCellPaddingXPx;
    101 
    102     // Folder
    103     public int folderBackgroundOffset;
    104     public int folderIconSizePx;
    105     public int folderIconPreviewPadding;
    106 
    107     // Folder cell
    108     public int folderCellWidthPx;
    109     public int folderCellHeightPx;
    110 
    111     // Folder child
    112     public int folderChildIconSizePx;
    113     public int folderChildTextSizePx;
    114     public int folderChildDrawablePaddingPx;
    115 
    116     // Hotseat
    117     public int hotseatCellHeightPx;
    118     // In portrait: size = height, in landscape: size = width
    119     public int hotseatBarSizePx;
    120     public int hotseatBarTopPaddingPx;
    121     public int hotseatBarBottomPaddingPx;
    122 
    123     public int hotseatBarLeftNavBarLeftPaddingPx;
    124     public int hotseatBarLeftNavBarRightPaddingPx;
    125 
    126     public int hotseatBarRightNavBarLeftPaddingPx;
    127     public int hotseatBarRightNavBarRightPaddingPx;
    128 
    129     // All apps
    130     public int allAppsCellHeightPx;
    131     public int allAppsNumCols;
    132     public int allAppsNumPredictiveCols;
    133     public int allAppsButtonVisualSize;
    134     public int allAppsIconSizePx;
    135     public int allAppsIconDrawablePaddingPx;
    136     public float allAppsIconTextSizePx;
    137 
    138     // Widgets
    139     public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
    140 
    141     // Drop Target
    142     public int dropTargetBarSizePx;
    143 
    144     // Insets
    145     private Rect mInsets = new Rect();
    146 
    147     // Listeners
    148     private ArrayList<LauncherLayoutChangeListener> mListeners = new ArrayList<>();
    149 
    150     // Icon badges
    151     public BadgeRenderer mBadgeRenderer;
    152 
    153     public DeviceProfile(Context context, InvariantDeviceProfile inv,
    154             Point minSize, Point maxSize,
    155             int width, int height, boolean isLandscape) {
    156 
    157         this.inv = inv;
    158         this.isLandscape = isLandscape;
    159 
    160         Resources res = context.getResources();
    161         DisplayMetrics dm = res.getDisplayMetrics();
    162 
    163         // Constants from resources
    164         isTablet = res.getBoolean(R.bool.is_tablet);
    165         isLargeTablet = res.getBoolean(R.bool.is_large_tablet);
    166         isPhone = !isTablet && !isLargeTablet;
    167 
    168         // Some more constants
    169         transposeLayoutWithOrientation =
    170                 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
    171 
    172         context = getContext(context, isVerticalBarLayout()
    173                 ? Configuration.ORIENTATION_LANDSCAPE
    174                 : Configuration.ORIENTATION_PORTRAIT);
    175         res = context.getResources();
    176 
    177 
    178         ComponentName cn = new ComponentName(context.getPackageName(),
    179                 this.getClass().getName());
    180         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
    181         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
    182         desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
    183         cellLayoutPaddingLeftRightPx =
    184                 res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
    185         cellLayoutBottomPaddingPx =
    186                 res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_bottom_padding);
    187         pageIndicatorSizePx = res.getDimensionPixelSize(
    188                 R.dimen.dynamic_grid_min_page_indicator_size);
    189         pageIndicatorLandLeftNavBarGutterPx = res.getDimensionPixelSize(
    190                 R.dimen.dynamic_grid_page_indicator_land_left_nav_bar_gutter_width);
    191         pageIndicatorLandRightNavBarGutterPx = res.getDimensionPixelSize(
    192                 R.dimen.dynamic_grid_page_indicator_land_right_nav_bar_gutter_width);
    193         pageIndicatorLandWorkspaceOffsetPx =
    194                 res.getDimensionPixelSize(R.dimen.all_apps_caret_workspace_offset);
    195         defaultPageSpacingPx =
    196                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
    197         topWorkspacePadding =
    198                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_top_padding);
    199         overviewModeMinIconZoneHeightPx =
    200                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
    201         overviewModeMaxIconZoneHeightPx =
    202                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
    203         overviewModeBarItemWidthPx =
    204                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
    205         overviewModeBarSpacerWidthPx =
    206                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
    207         overviewModeIconZoneRatio =
    208                 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
    209         iconDrawablePaddingOriginalPx =
    210                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
    211         dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
    212         workspaceSpringLoadedBottomSpace =
    213                 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
    214 
    215         workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
    216 
    217         hotseatBarTopPaddingPx =
    218                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
    219         hotseatBarBottomPaddingPx =
    220                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
    221         hotseatBarLeftNavBarRightPaddingPx = res.getDimensionPixelSize(
    222                 R.dimen.dynamic_grid_hotseat_land_left_nav_bar_right_padding);
    223         hotseatBarRightNavBarRightPaddingPx = res.getDimensionPixelSize(
    224                 R.dimen.dynamic_grid_hotseat_land_right_nav_bar_right_padding);
    225         hotseatBarLeftNavBarLeftPaddingPx = res.getDimensionPixelSize(
    226                 R.dimen.dynamic_grid_hotseat_land_left_nav_bar_left_padding);
    227         hotseatBarRightNavBarLeftPaddingPx = res.getDimensionPixelSize(
    228                 R.dimen.dynamic_grid_hotseat_land_right_nav_bar_left_padding);
    229         hotseatBarSizePx = isVerticalBarLayout()
    230                 ? Utilities.pxFromDp(inv.iconSize, dm)
    231                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_size)
    232                         + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx;
    233 
    234         // Determine sizes.
    235         widthPx = width;
    236         heightPx = height;
    237         if (isLandscape) {
    238             availableWidthPx = maxSize.x;
    239             availableHeightPx = minSize.y;
    240         } else {
    241             availableWidthPx = minSize.x;
    242             availableHeightPx = maxSize.y;
    243         }
    244 
    245         // Calculate all of the remaining variables.
    246         updateAvailableDimensions(dm, res);
    247 
    248         // Now that we have all of the variables calculated, we can tune certain sizes.
    249         float aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
    250         boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
    251         if (!isVerticalBarLayout() && isPhone && isTallDevice) {
    252             // We increase the hotseat size when there is extra space.
    253             // ie. For a display with a large aspect ratio, we can keep the icons on the workspace
    254             // in portrait mode closer together by adding more height to the hotseat.
    255             // Note: This calculation was created after noticing a pattern in the design spec.
    256             int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx;
    257             hotseatBarSizePx += extraSpace - pageIndicatorSizePx;
    258 
    259             // Recalculate the available dimensions using the new hotseat size.
    260             updateAvailableDimensions(dm, res);
    261         }
    262 
    263         computeAllAppsButtonSize(context);
    264 
    265         // This is done last, after iconSizePx is calculated above.
    266         mBadgeRenderer = new BadgeRenderer(context, iconSizePx);
    267     }
    268 
    269     DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
    270         // We take the minimum sizes of this profile and it's multi-window variant to ensure that
    271         // the system decor is always excluded.
    272         mwSize.set(Math.min(availableWidthPx, mwSize.x), Math.min(availableHeightPx, mwSize.y));
    273 
    274         // In multi-window mode, we can have widthPx = availableWidthPx
    275         // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
    276         // widthPx and heightPx values where it's needed.
    277         DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
    278                 isLandscape);
    279 
    280         // Hide labels on the workspace.
    281         profile.adjustToHideWorkspaceLabels();
    282 
    283         // We use these scales to measure and layout the widgets using their full invariant profile
    284         // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
    285         float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
    286         float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
    287         profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
    288 
    289         return profile;
    290     }
    291 
    292     public void addLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
    293         if (!mListeners.contains(listener)) {
    294             mListeners.add(listener);
    295         }
    296     }
    297 
    298     public void removeLauncherLayoutChangedListener(LauncherLayoutChangeListener listener) {
    299         if (mListeners.contains(listener)) {
    300             mListeners.remove(listener);
    301         }
    302     }
    303 
    304     /**
    305      * Adjusts the profile so that the labels on the Workspace are hidden.
    306      * It is important to call this method after the All Apps variables have been set.
    307      */
    308     private void adjustToHideWorkspaceLabels() {
    309         iconTextSizePx = 0;
    310         iconDrawablePaddingPx = 0;
    311         cellHeightPx = iconSizePx;
    312 
    313         // In normal cases, All Apps cell height should equal the Workspace cell height.
    314         // Since we are removing labels from the Workspace, we need to manually compute the
    315         // All Apps cell height.
    316         int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1);
    317         allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
    318                 + Utilities.calculateTextHeight(allAppsIconTextSizePx)
    319                 + topBottomPadding * 2;
    320     }
    321 
    322     /**
    323      * Determine the exact visual footprint of the all apps button, taking into account scaling
    324      * and internal padding of the drawable.
    325      */
    326     private void computeAllAppsButtonSize(Context context) {
    327         Resources res = context.getResources();
    328         float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
    329         allAppsButtonVisualSize = (int) (iconSizePx * (1 - padding)) - context.getResources()
    330                         .getDimensionPixelSize(R.dimen.all_apps_button_scale_down);
    331     }
    332 
    333     private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
    334         updateIconSize(1f, res, dm);
    335 
    336         // Check to see if the icons fit within the available height.  If not, then scale down.
    337         float usedHeight = (cellHeightPx * inv.numRows);
    338         int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
    339         if (usedHeight > maxHeight) {
    340             float scale = maxHeight / usedHeight;
    341             updateIconSize(scale, res, dm);
    342         }
    343         updateAvailableFolderCellDimensions(dm, res);
    344     }
    345 
    346     private void updateIconSize(float scale, Resources res, DisplayMetrics dm) {
    347         // Workspace
    348         float invIconSizePx = isVerticalBarLayout() ? inv.landscapeIconSize : inv.iconSize;
    349         iconSizePx = (int) (Utilities.pxFromDp(invIconSizePx, dm) * scale);
    350         iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
    351         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
    352 
    353         cellHeightPx = iconSizePx + iconDrawablePaddingPx
    354                 + Utilities.calculateTextHeight(iconTextSizePx);
    355         int cellYPadding = (getCellSize().y - cellHeightPx) / 2;
    356         if (iconDrawablePaddingPx > cellYPadding && !isVerticalBarLayout()
    357                 && !inMultiWindowMode()) {
    358             // Ensures that the label is closer to its corresponding icon. This is not an issue
    359             // with vertical bar layout or multi-window mode since the issue is handled separately
    360             // with their calls to {@link #adjustToHideWorkspaceLabels}.
    361             cellHeightPx -= (iconDrawablePaddingPx - cellYPadding);
    362             iconDrawablePaddingPx = cellYPadding;
    363         }
    364         cellWidthPx = iconSizePx + iconDrawablePaddingPx;
    365 
    366         // All apps
    367         allAppsIconTextSizePx = iconTextSizePx;
    368         allAppsIconSizePx = iconSizePx;
    369         allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
    370         allAppsCellHeightPx = getCellSize().y;
    371 
    372         if (isVerticalBarLayout()) {
    373             // Always hide the Workspace text with vertical bar layout.
    374             adjustToHideWorkspaceLabels();
    375         }
    376 
    377         // Hotseat
    378         if (isVerticalBarLayout()) {
    379             hotseatBarSizePx = iconSizePx;
    380         }
    381         hotseatCellHeightPx = iconSizePx;
    382 
    383         if (!isVerticalBarLayout()) {
    384             int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx
    385                     - pageIndicatorSizePx - topWorkspacePadding;
    386             float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
    387             workspaceSpringLoadShrinkFactor = Math.min(
    388                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f,
    389                     1 - (minRequiredHeight / expectedWorkspaceHeight));
    390         } else {
    391             workspaceSpringLoadShrinkFactor =
    392                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
    393         }
    394 
    395         // Folder icon
    396         folderBackgroundOffset = -iconDrawablePaddingPx;
    397         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
    398         folderIconPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
    399     }
    400 
    401     private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) {
    402         int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
    403                 + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
    404                 + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
    405 
    406         updateFolderCellSize(1f, dm, res);
    407 
    408         // Don't let the folder get too close to the edges of the screen.
    409         int folderMargin = edgeMarginPx;
    410 
    411         // Check if the icons fit within the available height.
    412         float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize;
    413         int maxHeight = availableHeightPx - getTotalWorkspacePadding().y - folderMargin;
    414         float scaleY = maxHeight / usedHeight;
    415 
    416         // Check if the icons fit within the available width.
    417         float usedWidth = folderCellWidthPx * inv.numFolderColumns;
    418         int maxWidth = availableWidthPx - getTotalWorkspacePadding().x - folderMargin;
    419         float scaleX = maxWidth / usedWidth;
    420 
    421         float scale = Math.min(scaleX, scaleY);
    422         if (scale < 1f) {
    423             updateFolderCellSize(scale, dm, res);
    424         }
    425     }
    426 
    427     private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) {
    428         folderChildIconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale);
    429         folderChildTextSizePx =
    430                 (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
    431 
    432         int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
    433         int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale);
    434         int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale);
    435 
    436         folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
    437         folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
    438         folderChildDrawablePaddingPx = Math.max(0,
    439                 (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
    440     }
    441 
    442     public void updateInsets(Rect insets) {
    443         mInsets.set(insets);
    444     }
    445 
    446     public void updateAppsViewNumCols() {
    447         allAppsNumCols = allAppsNumPredictiveCols = inv.numColumns;
    448     }
    449 
    450     /** Returns the width and height of the search bar, ignoring any padding. */
    451     public Point getSearchBarDimensForWidgetOpts() {
    452         if (isVerticalBarLayout()) {
    453             return new Point(dropTargetBarSizePx, availableHeightPx - 2 * edgeMarginPx);
    454         } else {
    455             int gap;
    456             if (isTablet) {
    457                 // Pad the left and right of the workspace to ensure consistent spacing
    458                 // between all icons
    459                 int width = getCurrentWidth();
    460                 // XXX: If the icon size changes across orientations, we will have to take
    461                 //      that into account here too.
    462                 gap = ((width - 2 * edgeMarginPx
    463                         - (inv.numColumns * cellWidthPx)) / (2 * (inv.numColumns + 1)))
    464                         + edgeMarginPx;
    465             } else {
    466                 gap = desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right;
    467             }
    468             return new Point(availableWidthPx - 2 * gap, dropTargetBarSizePx);
    469         }
    470     }
    471 
    472     public Point getCellSize() {
    473         Point result = new Point();
    474         // Since we are only concerned with the overall padding, layout direction does
    475         // not matter.
    476         Point padding = getTotalWorkspacePadding();
    477         result.x = calculateCellWidth(availableWidthPx - padding.x
    478                 - cellLayoutPaddingLeftRightPx * 2, inv.numColumns);
    479         result.y = calculateCellHeight(availableHeightPx - padding.y
    480                 - cellLayoutBottomPaddingPx, inv.numRows);
    481         return result;
    482     }
    483 
    484     public Point getTotalWorkspacePadding() {
    485         Rect padding = getWorkspacePadding(null);
    486         return new Point(padding.left + padding.right, padding.top + padding.bottom);
    487     }
    488 
    489     /**
    490      * Returns the workspace padding in the specified orientation.
    491      */
    492     public Rect getWorkspacePadding(Rect recycle) {
    493         Rect padding = recycle == null ? new Rect() : recycle;
    494         if (isVerticalBarLayout()) {
    495             if (mInsets.left > 0) {
    496                 padding.set(mInsets.left + pageIndicatorLandLeftNavBarGutterPx,
    497                         0,
    498                         hotseatBarSizePx + hotseatBarLeftNavBarRightPaddingPx
    499                                 + hotseatBarLeftNavBarLeftPaddingPx
    500                                 - mInsets.left,
    501                         edgeMarginPx);
    502             } else {
    503                 padding.set(pageIndicatorLandRightNavBarGutterPx,
    504                         0,
    505                         hotseatBarSizePx + hotseatBarRightNavBarRightPaddingPx
    506                                 + hotseatBarRightNavBarLeftPaddingPx,
    507                         edgeMarginPx);
    508             }
    509         } else {
    510             int paddingBottom = hotseatBarSizePx + pageIndicatorSizePx;
    511             if (isTablet) {
    512                 // Pad the left and right of the workspace to ensure consistent spacing
    513                 // between all icons
    514                 int width = getCurrentWidth();
    515                 int height = getCurrentHeight();
    516                 // The amount of screen space available for left/right padding.
    517                 int availablePaddingX = Math.max(0, width - ((inv.numColumns * cellWidthPx) +
    518                         ((inv.numColumns - 1) * cellWidthPx)));
    519                 availablePaddingX = (int) Math.min(availablePaddingX,
    520                             width * MAX_HORIZONTAL_PADDING_PERCENT);
    521                 int availablePaddingY = Math.max(0, height - topWorkspacePadding - paddingBottom
    522                         - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
    523                         - hotseatBarBottomPaddingPx);
    524                 padding.set(availablePaddingX / 2, topWorkspacePadding + availablePaddingY / 2,
    525                         availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
    526             } else {
    527                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
    528                 padding.set(desiredWorkspaceLeftRightMarginPx,
    529                         topWorkspacePadding,
    530                         desiredWorkspaceLeftRightMarginPx,
    531                         paddingBottom);
    532             }
    533         }
    534         return padding;
    535     }
    536 
    537     /**
    538      * @return the bounds for which the open folders should be contained within
    539      */
    540     public Rect getAbsoluteOpenFolderBounds() {
    541         if (isVerticalBarLayout()) {
    542             // Folders should only appear right of the drop target bar and left of the hotseat
    543             return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx,
    544                     mInsets.top,
    545                     mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx,
    546                     mInsets.top + availableHeightPx);
    547         } else {
    548             // Folders should only appear below the drop target bar and above the hotseat
    549             return new Rect(mInsets.left,
    550                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
    551                     mInsets.left + availableWidthPx,
    552                     mInsets.top + availableHeightPx - hotseatBarSizePx
    553                             - pageIndicatorSizePx - edgeMarginPx);
    554         }
    555     }
    556 
    557     private int getWorkspacePageSpacing() {
    558         if (isVerticalBarLayout() || isLargeTablet) {
    559             // In landscape mode the page spacing is set to the default.
    560             return defaultPageSpacingPx;
    561         } else {
    562             // In portrait, we want the pages spaced such that there is no
    563             // overhang of the previous / next page into the current page viewport.
    564             // We assume symmetrical padding in portrait mode.
    565             return Math.max(defaultPageSpacingPx, getWorkspacePadding(null).left + 1);
    566         }
    567     }
    568 
    569     int getOverviewModeButtonBarHeight() {
    570         int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
    571         return Utilities.boundToRange(zoneHeight,
    572                 overviewModeMinIconZoneHeightPx,
    573                 overviewModeMaxIconZoneHeightPx);
    574     }
    575 
    576     public static int calculateCellWidth(int width, int countX) {
    577         return width / countX;
    578     }
    579     public static int calculateCellHeight(int height, int countY) {
    580         return height / countY;
    581     }
    582 
    583     /**
    584      * When {@code true}, the device is in landscape mode and the hotseat is on the right column.
    585      * When {@code false}, either device is in portrait mode or the device is in landscape mode and
    586      * the hotseat is on the bottom row.
    587      */
    588     public boolean isVerticalBarLayout() {
    589         return isLandscape && transposeLayoutWithOrientation;
    590     }
    591 
    592     boolean shouldFadeAdjacentWorkspaceScreens() {
    593         return isVerticalBarLayout() || isLargeTablet;
    594     }
    595 
    596     private int getVisibleChildCount(ViewGroup parent) {
    597         int visibleChildren = 0;
    598         for (int i = 0; i < parent.getChildCount(); i++) {
    599             if (parent.getChildAt(i).getVisibility() != View.GONE) {
    600                 visibleChildren++;
    601             }
    602         }
    603         return visibleChildren;
    604     }
    605 
    606     public void layout(Launcher launcher, boolean notifyListeners) {
    607         FrameLayout.LayoutParams lp;
    608         boolean hasVerticalBarLayout = isVerticalBarLayout();
    609 
    610         // Layout the search bar space
    611         Point searchBarBounds = getSearchBarDimensForWidgetOpts();
    612         View searchBar = launcher.getDropTargetBar();
    613         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
    614         lp.width = searchBarBounds.x;
    615         lp.height = searchBarBounds.y;
    616         lp.topMargin = mInsets.top + edgeMarginPx;
    617         searchBar.setLayoutParams(lp);
    618 
    619         // Layout the workspace
    620         PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
    621         Rect workspacePadding = getWorkspacePadding(null);
    622         workspace.setPadding(workspacePadding.left, workspacePadding.top, workspacePadding.right,
    623                 workspacePadding.bottom);
    624         workspace.setPageSpacing(getWorkspacePageSpacing());
    625 
    626         // Layout the hotseat
    627         Hotseat hotseat = (Hotseat) launcher.findViewById(R.id.hotseat);
    628         lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
    629         // We want the edges of the hotseat to line up with the edges of the workspace, but the
    630         // icons in the hotseat are a different size, and so don't line up perfectly. To account for
    631         // this, we pad the left and right of the hotseat with half of the difference of a workspace
    632         // cell vs a hotseat cell.
    633         float workspaceCellWidth = (float) getCurrentWidth() / inv.numColumns;
    634         float hotseatCellWidth = (float) getCurrentWidth() / inv.numHotseatIcons;
    635         int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
    636         if (hasVerticalBarLayout) {
    637             // Vertical hotseat -- The hotseat is fixed in the layout to be on the right of the
    638             //                     screen regardless of RTL
    639             int paddingRight = mInsets.left > 0
    640                     ? hotseatBarLeftNavBarRightPaddingPx
    641                     : hotseatBarRightNavBarRightPaddingPx;
    642             int paddingLeft = mInsets.left > 0
    643                     ? hotseatBarLeftNavBarLeftPaddingPx
    644                     : hotseatBarRightNavBarLeftPaddingPx;
    645 
    646             lp.gravity = Gravity.RIGHT;
    647             lp.width = hotseatBarSizePx + mInsets.left + mInsets.right
    648                     + paddingLeft + paddingRight;
    649             lp.height = LayoutParams.MATCH_PARENT;
    650 
    651             hotseat.getLayout().setPadding(mInsets.left + cellLayoutPaddingLeftRightPx
    652                             + paddingLeft,
    653                     mInsets.top,
    654                     mInsets.right + cellLayoutPaddingLeftRightPx + paddingRight,
    655                     workspacePadding.bottom + cellLayoutBottomPaddingPx);
    656         } else if (isTablet) {
    657             // Pad the hotseat with the workspace padding calculated above
    658             lp.gravity = Gravity.BOTTOM;
    659             lp.width = LayoutParams.MATCH_PARENT;
    660             lp.height = hotseatBarSizePx + mInsets.bottom;
    661             hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left
    662                             + cellLayoutPaddingLeftRightPx,
    663                     hotseatBarTopPaddingPx,
    664                     hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx,
    665                     hotseatBarBottomPaddingPx + mInsets.bottom + cellLayoutBottomPaddingPx);
    666         } else {
    667             // For phones, layout the hotseat without any bottom margin
    668             // to ensure that we have space for the folders
    669             lp.gravity = Gravity.BOTTOM;
    670             lp.width = LayoutParams.MATCH_PARENT;
    671             lp.height = hotseatBarSizePx + mInsets.bottom;
    672             hotseat.getLayout().setPadding(hotseatAdjustment + workspacePadding.left
    673                             + cellLayoutPaddingLeftRightPx,
    674                     hotseatBarTopPaddingPx,
    675                     hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx,
    676                     hotseatBarBottomPaddingPx + mInsets.bottom + cellLayoutBottomPaddingPx);
    677         }
    678         hotseat.setLayoutParams(lp);
    679 
    680         // Layout the page indicators
    681         View pageIndicator = launcher.findViewById(R.id.page_indicator);
    682         if (pageIndicator != null) {
    683             lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
    684             if (isVerticalBarLayout()) {
    685                 if (mInsets.left > 0) {
    686                     lp.leftMargin = mInsets.left;
    687                 } else {
    688                     lp.leftMargin = pageIndicatorLandWorkspaceOffsetPx;
    689                 }
    690                 lp.bottomMargin = workspacePadding.bottom;
    691             } else {
    692                 // Put the page indicators above the hotseat
    693                 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
    694                 lp.height = pageIndicatorSizePx;
    695                 lp.bottomMargin = hotseatBarSizePx + mInsets.bottom;
    696             }
    697             pageIndicator.setLayoutParams(lp);
    698         }
    699 
    700         // Layout the Overview Mode
    701         ViewGroup overviewMode = launcher.getOverviewPanel();
    702         if (overviewMode != null) {
    703             int visibleChildCount = getVisibleChildCount(overviewMode);
    704             int totalItemWidth = visibleChildCount * overviewModeBarItemWidthPx;
    705             int maxWidth = totalItemWidth + (visibleChildCount - 1) * overviewModeBarSpacerWidthPx;
    706 
    707             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
    708             lp.width = Math.min(availableWidthPx, maxWidth);
    709             lp.height = getOverviewModeButtonBarHeight();
    710             lp.bottomMargin = mInsets.bottom;
    711             overviewMode.setLayoutParams(lp);
    712         }
    713 
    714         // Layout the AllAppsRecyclerView
    715         View view = launcher.findViewById(R.id.apps_list_view);
    716         int paddingLeftRight = desiredWorkspaceLeftRightMarginPx + cellLayoutPaddingLeftRightPx;
    717         view.setPadding(paddingLeftRight, view.getPaddingTop(), paddingLeftRight,
    718                 view.getPaddingBottom());
    719 
    720         if (notifyListeners) {
    721             for (int i = mListeners.size() - 1; i >= 0; i--) {
    722                 mListeners.get(i).onLauncherLayoutChanged();
    723             }
    724         }
    725     }
    726 
    727     private int getCurrentWidth() {
    728         return isLandscape
    729                 ? Math.max(widthPx, heightPx)
    730                 : Math.min(widthPx, heightPx);
    731     }
    732 
    733     private int getCurrentHeight() {
    734         return isLandscape
    735                 ? Math.min(widthPx, heightPx)
    736                 : Math.max(widthPx, heightPx);
    737     }
    738 
    739     public int getCellHeight(@ContainerType int containerType) {
    740         switch (containerType) {
    741             case CellLayout.WORKSPACE:
    742                 return cellHeightPx;
    743             case CellLayout.FOLDER:
    744                 return folderCellHeightPx;
    745             case CellLayout.HOTSEAT:
    746                 return hotseatCellHeightPx;
    747             default:
    748                 // ??
    749                 return 0;
    750         }
    751     }
    752 
    753     /**
    754      * @return the left/right paddings for all containers.
    755      */
    756     public final int[] getContainerPadding() {
    757         // No paddings for portrait phone
    758         if (isPhone && !isVerticalBarLayout()) {
    759             return new int[] {0, 0};
    760         }
    761 
    762         // In landscape, we match the width of the workspace
    763         Rect padding = getWorkspacePadding(null);
    764         return new int[] { padding.left - mInsets.left, padding.right + mInsets.left};
    765     }
    766 
    767     public boolean inMultiWindowMode() {
    768         return this != inv.landscapeProfile && this != inv.portraitProfile;
    769     }
    770 
    771     public boolean shouldIgnoreLongPressToOverview(float touchX) {
    772         boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeMarginPx;
    773         boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeMarginPx);
    774         return !inMultiWindowMode() && (touchedLhsEdge || touchedRhsEdge);
    775     }
    776 
    777     private static Context getContext(Context c, int orientation) {
    778         Configuration context = new Configuration(c.getResources().getConfiguration());
    779         context.orientation = orientation;
    780         return c.createConfigurationContext(context);
    781 
    782     }
    783 }
    784