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.Surface;
     29 import android.view.WindowManager;
     30 
     31 import com.android.launcher3.CellLayout.ContainerType;
     32 import com.android.launcher3.badge.BadgeRenderer;
     33 import com.android.launcher3.graphics.IconNormalizer;
     34 
     35 public class DeviceProfile {
     36 
     37     public final InvariantDeviceProfile inv;
     38 
     39     // Device properties
     40     public final boolean isTablet;
     41     public final boolean isLargeTablet;
     42     public final boolean isPhone;
     43     public final boolean transposeLayoutWithOrientation;
     44 
     45     // Device properties in current orientation
     46     public final boolean isLandscape;
     47     public final boolean isMultiWindowMode;
     48 
     49     public final int widthPx;
     50     public final int heightPx;
     51     public final int availableWidthPx;
     52     public final int availableHeightPx;
     53     /**
     54      * The maximum amount of left/right workspace padding as a percentage of the screen width.
     55      * To be clear, this means that up to 7% of the screen width can be used as left padding, and
     56      * 7% of the screen width can be used as right padding.
     57      */
     58     private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f;
     59 
     60     private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
     61 
     62     // Workspace
     63     public final int desiredWorkspaceLeftRightMarginPx;
     64     public final int cellLayoutPaddingLeftRightPx;
     65     public final int cellLayoutBottomPaddingPx;
     66     public final int edgeMarginPx;
     67     public final Rect defaultWidgetPadding;
     68     public final int defaultPageSpacingPx;
     69     private final int topWorkspacePadding;
     70     public float workspaceSpringLoadShrinkFactor;
     71     public final int workspaceSpringLoadedBottomSpace;
     72 
     73     // Drag handle
     74     public final int verticalDragHandleSizePx;
     75 
     76     // Workspace icons
     77     public int iconSizePx;
     78     public int iconTextSizePx;
     79     public int iconDrawablePaddingPx;
     80     public int iconDrawablePaddingOriginalPx;
     81 
     82     public int cellWidthPx;
     83     public int cellHeightPx;
     84     public int workspaceCellPaddingXPx;
     85 
     86     // Folder
     87     public int folderIconSizePx;
     88     public int folderIconOffsetYPx;
     89 
     90     // Folder cell
     91     public int folderCellWidthPx;
     92     public int folderCellHeightPx;
     93 
     94     // Folder child
     95     public int folderChildIconSizePx;
     96     public int folderChildTextSizePx;
     97     public int folderChildDrawablePaddingPx;
     98 
     99     // Hotseat
    100     public int hotseatCellHeightPx;
    101     // In portrait: size = height, in landscape: size = width
    102     public int hotseatBarSizePx;
    103     public final int hotseatBarTopPaddingPx;
    104     public final int hotseatBarBottomPaddingPx;
    105     public final int hotseatBarSidePaddingPx;
    106 
    107     // All apps
    108     public int allAppsCellHeightPx;
    109     public int allAppsIconSizePx;
    110     public int allAppsIconDrawablePaddingPx;
    111     public float allAppsIconTextSizePx;
    112 
    113     // Widgets
    114     public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
    115 
    116     // Drop Target
    117     public int dropTargetBarSizePx;
    118 
    119     // Insets
    120     private final Rect mInsets = new Rect();
    121     public final Rect workspacePadding = new Rect();
    122     private final Rect mHotseatPadding = new Rect();
    123     private boolean mIsSeascape;
    124 
    125     // Icon badges
    126     public BadgeRenderer mBadgeRenderer;
    127 
    128     public DeviceProfile(Context context, InvariantDeviceProfile inv,
    129             Point minSize, Point maxSize,
    130             int width, int height, boolean isLandscape, boolean isMultiWindowMode) {
    131 
    132         this.inv = inv;
    133         this.isLandscape = isLandscape;
    134         this.isMultiWindowMode = isMultiWindowMode;
    135 
    136         Resources res = context.getResources();
    137         DisplayMetrics dm = res.getDisplayMetrics();
    138 
    139         // Constants from resources
    140         isTablet = res.getBoolean(R.bool.is_tablet);
    141         isLargeTablet = res.getBoolean(R.bool.is_large_tablet);
    142         isPhone = !isTablet && !isLargeTablet;
    143 
    144         // Some more constants
    145         transposeLayoutWithOrientation =
    146                 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
    147 
    148         context = getContext(context, isVerticalBarLayout()
    149                 ? Configuration.ORIENTATION_LANDSCAPE
    150                 : Configuration.ORIENTATION_PORTRAIT);
    151         res = context.getResources();
    152 
    153 
    154         ComponentName cn = new ComponentName(context.getPackageName(),
    155                 this.getClass().getName());
    156         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
    157         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
    158         desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
    159         cellLayoutPaddingLeftRightPx =
    160                 res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
    161         cellLayoutBottomPaddingPx =
    162                 res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_bottom_padding);
    163         verticalDragHandleSizePx = res.getDimensionPixelSize(
    164                 R.dimen.vertical_drag_handle_size);
    165         defaultPageSpacingPx =
    166                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
    167         topWorkspacePadding =
    168                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_top_padding);
    169         iconDrawablePaddingOriginalPx =
    170                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
    171         dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
    172         workspaceSpringLoadedBottomSpace =
    173                 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
    174 
    175         workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
    176 
    177         hotseatBarTopPaddingPx =
    178                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
    179         hotseatBarBottomPaddingPx =
    180                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
    181         hotseatBarSidePaddingPx =
    182                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
    183         hotseatBarSizePx = isVerticalBarLayout()
    184                 ? Utilities.pxFromDp(inv.iconSize, dm)
    185                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_size)
    186                         + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx;
    187 
    188         // Determine sizes.
    189         widthPx = width;
    190         heightPx = height;
    191         if (isLandscape) {
    192             availableWidthPx = maxSize.x;
    193             availableHeightPx = minSize.y;
    194         } else {
    195             availableWidthPx = minSize.x;
    196             availableHeightPx = maxSize.y;
    197         }
    198 
    199         // Calculate all of the remaining variables.
    200         updateAvailableDimensions(dm, res);
    201 
    202         // Now that we have all of the variables calculated, we can tune certain sizes.
    203         float aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
    204         boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
    205         if (!isVerticalBarLayout() && isPhone && isTallDevice) {
    206             // We increase the hotseat size when there is extra space.
    207             // ie. For a display with a large aspect ratio, we can keep the icons on the workspace
    208             // in portrait mode closer together by adding more height to the hotseat.
    209             // Note: This calculation was created after noticing a pattern in the design spec.
    210             int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx;
    211             hotseatBarSizePx += extraSpace - verticalDragHandleSizePx;
    212 
    213             // Recalculate the available dimensions using the new hotseat size.
    214             updateAvailableDimensions(dm, res);
    215         }
    216         updateWorkspacePadding();
    217 
    218         // This is done last, after iconSizePx is calculated above.
    219         mBadgeRenderer = new BadgeRenderer(iconSizePx);
    220     }
    221 
    222     public DeviceProfile copy(Context context) {
    223         Point size = new Point(availableWidthPx, availableHeightPx);
    224         return new DeviceProfile(context, inv, size, size, widthPx, heightPx, isLandscape,
    225                 isMultiWindowMode);
    226     }
    227 
    228     public DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
    229         // We take the minimum sizes of this profile and it's multi-window variant to ensure that
    230         // the system decor is always excluded.
    231         mwSize.set(Math.min(availableWidthPx, mwSize.x), Math.min(availableHeightPx, mwSize.y));
    232 
    233         // In multi-window mode, we can have widthPx = availableWidthPx
    234         // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
    235         // widthPx and heightPx values where it's needed.
    236         DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
    237                 isLandscape, true);
    238 
    239         // If there isn't enough vertical cell padding with the labels displayed, hide the labels.
    240         float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx
    241                 - iconDrawablePaddingPx - profile.iconTextSizePx;
    242         if (workspaceCellPaddingY < profile.iconDrawablePaddingPx * 2) {
    243             profile.adjustToHideWorkspaceLabels();
    244         }
    245 
    246         // We use these scales to measure and layout the widgets using their full invariant profile
    247         // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
    248         float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
    249         float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
    250         profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
    251         profile.updateWorkspacePadding();
    252 
    253         return profile;
    254     }
    255 
    256     /**
    257      * Inverse of {@link #getMultiWindowProfile(Context, Point)}
    258      * @return device profile corresponding to the current orientation in non multi-window mode.
    259      */
    260     public DeviceProfile getFullScreenProfile() {
    261         return isLandscape ? inv.landscapeProfile : inv.portraitProfile;
    262     }
    263 
    264     /**
    265      * Adjusts the profile so that the labels on the Workspace are hidden.
    266      * It is important to call this method after the All Apps variables have been set.
    267      */
    268     private void adjustToHideWorkspaceLabels() {
    269         iconTextSizePx = 0;
    270         iconDrawablePaddingPx = 0;
    271         cellHeightPx = iconSizePx;
    272 
    273         // In normal cases, All Apps cell height should equal the Workspace cell height.
    274         // Since we are removing labels from the Workspace, we need to manually compute the
    275         // All Apps cell height.
    276         int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1);
    277         allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
    278                 + Utilities.calculateTextHeight(allAppsIconTextSizePx)
    279                 + topBottomPadding * 2;
    280     }
    281 
    282     private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
    283         updateIconSize(1f, res, dm);
    284 
    285         // Check to see if the icons fit within the available height.  If not, then scale down.
    286         float usedHeight = (cellHeightPx * inv.numRows);
    287         int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
    288         if (usedHeight > maxHeight) {
    289             float scale = maxHeight / usedHeight;
    290             updateIconSize(scale, res, dm);
    291         }
    292         updateAvailableFolderCellDimensions(dm, res);
    293     }
    294 
    295     private void updateIconSize(float scale, Resources res, DisplayMetrics dm) {
    296         // Workspace
    297         final boolean isVerticalLayout = isVerticalBarLayout();
    298         float invIconSizePx = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
    299         iconSizePx = (int) (Utilities.pxFromDp(invIconSizePx, dm) * scale);
    300         iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
    301         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
    302 
    303         cellHeightPx = iconSizePx + iconDrawablePaddingPx
    304                 + Utilities.calculateTextHeight(iconTextSizePx);
    305         int cellYPadding = (getCellSize().y - cellHeightPx) / 2;
    306         if (iconDrawablePaddingPx > cellYPadding && !isVerticalLayout
    307                 && !isMultiWindowMode) {
    308             // Ensures that the label is closer to its corresponding icon. This is not an issue
    309             // with vertical bar layout or multi-window mode since the issue is handled separately
    310             // with their calls to {@link #adjustToHideWorkspaceLabels}.
    311             cellHeightPx -= (iconDrawablePaddingPx - cellYPadding);
    312             iconDrawablePaddingPx = cellYPadding;
    313         }
    314         cellWidthPx = iconSizePx + iconDrawablePaddingPx;
    315 
    316         // All apps
    317         allAppsIconTextSizePx = iconTextSizePx;
    318         allAppsIconSizePx = iconSizePx;
    319         allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
    320         allAppsCellHeightPx = getCellSize().y;
    321 
    322         if (isVerticalLayout) {
    323             // Always hide the Workspace text with vertical bar layout.
    324             adjustToHideWorkspaceLabels();
    325         }
    326 
    327         // Hotseat
    328         if (isVerticalLayout) {
    329             hotseatBarSizePx = iconSizePx;
    330         }
    331         hotseatCellHeightPx = iconSizePx;
    332 
    333         if (!isVerticalLayout) {
    334             int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx
    335                     - verticalDragHandleSizePx - topWorkspacePadding;
    336             float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
    337             workspaceSpringLoadShrinkFactor = Math.min(
    338                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f,
    339                     1 - (minRequiredHeight / expectedWorkspaceHeight));
    340         } else {
    341             workspaceSpringLoadShrinkFactor =
    342                     res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
    343         }
    344 
    345         // Folder icon
    346         folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
    347         folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
    348     }
    349 
    350     private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) {
    351         int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
    352                 + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
    353                 + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
    354 
    355         updateFolderCellSize(1f, dm, res);
    356 
    357         // Don't let the folder get too close to the edges of the screen.
    358         int folderMargin = edgeMarginPx;
    359         Point totalWorkspacePadding = getTotalWorkspacePadding();
    360 
    361         // Check if the icons fit within the available height.
    362         float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize;
    363         int maxHeight = availableHeightPx - totalWorkspacePadding.y - folderMargin;
    364         float scaleY = maxHeight / usedHeight;
    365 
    366         // Check if the icons fit within the available width.
    367         float usedWidth = folderCellWidthPx * inv.numFolderColumns;
    368         int maxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
    369         float scaleX = maxWidth / usedWidth;
    370 
    371         float scale = Math.min(scaleX, scaleY);
    372         if (scale < 1f) {
    373             updateFolderCellSize(scale, dm, res);
    374         }
    375     }
    376 
    377     private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) {
    378         folderChildIconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale);
    379         folderChildTextSizePx =
    380                 (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
    381 
    382         int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
    383         int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale);
    384         int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale);
    385 
    386         folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
    387         folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
    388         folderChildDrawablePaddingPx = Math.max(0,
    389                 (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
    390     }
    391 
    392     public void updateInsets(Rect insets) {
    393         mInsets.set(insets);
    394         updateWorkspacePadding();
    395     }
    396 
    397     public Rect getInsets() {
    398         return mInsets;
    399     }
    400 
    401     public Point getCellSize() {
    402         Point result = new Point();
    403         // Since we are only concerned with the overall padding, layout direction does
    404         // not matter.
    405         Point padding = getTotalWorkspacePadding();
    406         result.x = calculateCellWidth(availableWidthPx - padding.x
    407                 - cellLayoutPaddingLeftRightPx * 2, inv.numColumns);
    408         result.y = calculateCellHeight(availableHeightPx - padding.y
    409                 - cellLayoutBottomPaddingPx, inv.numRows);
    410         return result;
    411     }
    412 
    413     public Point getTotalWorkspacePadding() {
    414         updateWorkspacePadding();
    415         return new Point(workspacePadding.left + workspacePadding.right,
    416                 workspacePadding.top + workspacePadding.bottom);
    417     }
    418 
    419     /**
    420      * Updates {@link #workspacePadding} as a result of any internal value change to reflect the
    421      * new workspace padding
    422      */
    423     private void updateWorkspacePadding() {
    424         Rect padding = workspacePadding;
    425         if (isVerticalBarLayout()) {
    426             padding.top = 0;
    427             padding.bottom = edgeMarginPx;
    428             padding.left = hotseatBarSidePaddingPx;
    429             padding.right = hotseatBarSidePaddingPx;
    430             if (isSeascape()) {
    431                 padding.left += hotseatBarSizePx;
    432                 padding.right += verticalDragHandleSizePx;
    433             } else {
    434                 padding.left += verticalDragHandleSizePx;
    435                 padding.right += hotseatBarSizePx;
    436             }
    437         } else {
    438             int paddingBottom = hotseatBarSizePx + verticalDragHandleSizePx;
    439             if (isTablet) {
    440                 // Pad the left and right of the workspace to ensure consistent spacing
    441                 // between all icons
    442                 // The amount of screen space available for left/right padding.
    443                 int availablePaddingX = Math.max(0, widthPx - ((inv.numColumns * cellWidthPx) +
    444                         ((inv.numColumns - 1) * cellWidthPx)));
    445                 availablePaddingX = (int) Math.min(availablePaddingX,
    446                         widthPx * MAX_HORIZONTAL_PADDING_PERCENT);
    447                 int availablePaddingY = Math.max(0, heightPx - topWorkspacePadding - paddingBottom
    448                         - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
    449                         - hotseatBarBottomPaddingPx);
    450                 padding.set(availablePaddingX / 2, topWorkspacePadding + availablePaddingY / 2,
    451                         availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
    452             } else {
    453                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
    454                 padding.set(desiredWorkspaceLeftRightMarginPx,
    455                         topWorkspacePadding,
    456                         desiredWorkspaceLeftRightMarginPx,
    457                         paddingBottom);
    458             }
    459         }
    460     }
    461 
    462     public Rect getHotseatLayoutPadding() {
    463         if (isVerticalBarLayout()) {
    464             if (isSeascape()) {
    465                 mHotseatPadding.set(
    466                         mInsets.left, mInsets.top, hotseatBarSidePaddingPx, mInsets.bottom);
    467             } else {
    468                 mHotseatPadding.set(
    469                         hotseatBarSidePaddingPx, mInsets.top, mInsets.right, mInsets.bottom);
    470             }
    471         } else {
    472 
    473             // We want the edges of the hotseat to line up with the edges of the workspace, but the
    474             // icons in the hotseat are a different size, and so don't line up perfectly. To account
    475             // for this, we pad the left and right of the hotseat with half of the difference of a
    476             // workspace cell vs a hotseat cell.
    477             float workspaceCellWidth = (float) widthPx / inv.numColumns;
    478             float hotseatCellWidth = (float) widthPx / inv.numHotseatIcons;
    479             int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
    480             mHotseatPadding.set(
    481                     hotseatAdjustment + workspacePadding.left + cellLayoutPaddingLeftRightPx,
    482                     hotseatBarTopPaddingPx,
    483                     hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx,
    484                     hotseatBarBottomPaddingPx + mInsets.bottom + cellLayoutBottomPaddingPx);
    485         }
    486         return mHotseatPadding;
    487     }
    488 
    489     /**
    490      * @return the bounds for which the open folders should be contained within
    491      */
    492     public Rect getAbsoluteOpenFolderBounds() {
    493         if (isVerticalBarLayout()) {
    494             // Folders should only appear right of the drop target bar and left of the hotseat
    495             return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx,
    496                     mInsets.top,
    497                     mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx,
    498                     mInsets.top + availableHeightPx);
    499         } else {
    500             // Folders should only appear below the drop target bar and above the hotseat
    501             return new Rect(mInsets.left + edgeMarginPx,
    502                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
    503                     mInsets.left + availableWidthPx - edgeMarginPx,
    504                     mInsets.top + availableHeightPx - hotseatBarSizePx
    505                             - verticalDragHandleSizePx - edgeMarginPx);
    506         }
    507     }
    508 
    509     public static int calculateCellWidth(int width, int countX) {
    510         return width / countX;
    511     }
    512     public static int calculateCellHeight(int height, int countY) {
    513         return height / countY;
    514     }
    515 
    516     /**
    517      * When {@code true}, the device is in landscape mode and the hotseat is on the right column.
    518      * When {@code false}, either device is in portrait mode or the device is in landscape mode and
    519      * the hotseat is on the bottom row.
    520      */
    521     public boolean isVerticalBarLayout() {
    522         return isLandscape && transposeLayoutWithOrientation;
    523     }
    524 
    525     /**
    526      * Updates orientation information and returns true if it has changed from the previous value.
    527      */
    528     public boolean updateIsSeascape(WindowManager wm) {
    529         if (isVerticalBarLayout()) {
    530             boolean isSeascape = wm.getDefaultDisplay().getRotation() == Surface.ROTATION_270;
    531             if (mIsSeascape != isSeascape) {
    532                 mIsSeascape = isSeascape;
    533                 return true;
    534             }
    535         }
    536         return false;
    537     }
    538 
    539     public boolean isSeascape() {
    540         return isVerticalBarLayout() && mIsSeascape;
    541     }
    542 
    543     public boolean shouldFadeAdjacentWorkspaceScreens() {
    544         return isVerticalBarLayout() || isLargeTablet;
    545     }
    546 
    547     public int getCellHeight(@ContainerType int containerType) {
    548         switch (containerType) {
    549             case CellLayout.WORKSPACE:
    550                 return cellHeightPx;
    551             case CellLayout.FOLDER:
    552                 return folderCellHeightPx;
    553             case CellLayout.HOTSEAT:
    554                 return hotseatCellHeightPx;
    555             default:
    556                 // ??
    557                 return 0;
    558         }
    559     }
    560 
    561     private static Context getContext(Context c, int orientation) {
    562         Configuration context = new Configuration(c.getResources().getConfiguration());
    563         context.orientation = orientation;
    564         return c.createConfigurationContext(context);
    565     }
    566 
    567     /**
    568      * Callback when a component changes the DeviceProfile associated with it, as a result of
    569      * configuration change
    570      */
    571     public interface OnDeviceProfileChangeListener {
    572 
    573         /**
    574          * Called when the device profile is reassigned. Note that for layout and measurements, it
    575          * is sufficient to listen for inset changes. Use this callback when you need to perform
    576          * a one time operation.
    577          */
    578         void onDeviceProfileChanged(DeviceProfile dp);
    579     }
    580 }
    581