content: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.launcher3;
  
  import android.content.Context;
  import android.content.res.Configuration;
  import android.content.res.Resources;
  import android.graphics.Point;
  import android.graphics.PointF;
  import android.graphics.Rect;
  import android.util.DisplayMetrics;
  import android.view.Surface;
  
  import androidx.annotation.Nullable;
  
  import com.android.launcher3.CellLayout.ContainerType;
  import com.android.launcher3.config.FeatureFlags;
  import com.android.launcher3.graphics.IconShape;
  import com.android.launcher3.icons.DotRenderer;
  import com.android.launcher3.icons.IconNormalizer;
  import com.android.launcher3.util.DefaultDisplay;
  
  public class DeviceProfile {
  
      public final InvariantDeviceProfile inv;
      // IDP with no grid override values.
      @Nullable private final InvariantDeviceProfile originalIdp;
  
      // Device properties
      public final boolean isTablet;
      public final boolean isLargeTablet;
      public final boolean isPhone;
      public final boolean transposeLayoutWithOrientation;
  
      // Device properties in current orientation
      public final boolean isLandscape;
      public final boolean isMultiWindowMode;
  
      public final int widthPx;
      public final int heightPx;
      public final int availableWidthPx;
      public final int availableHeightPx;
  
      public final float aspectRatio;
  
      /**
       * The maximum amount of left/right workspace padding as a percentage of the screen width.
       * To be clear, this means that up to 7% of the screen width can be used as left padding, and
       * 7% of the screen width can be used as right padding.
       */
      private static final float MAX_HORIZONTAL_PADDING_PERCENT = 0.14f;
  
      private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
  
      // To evenly space the icons, increase the left/right margins for tablets in portrait mode.
      private static final int PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER = 4;
  
      // Workspace
      public final int desiredWorkspaceLeftRightMarginPx;
      public final int cellLayoutPaddingLeftRightPx;
      public final int cellLayoutBottomPaddingPx;
      public final int edgeMarginPx;
      public float workspaceSpringLoadShrinkFactor;
      public final int workspaceSpringLoadedBottomSpace;
  
      // Drag handle
      public final int verticalDragHandleSizePx;
      private final int verticalDragHandleOverlapWorkspace;
  
      // Workspace icons
      public int iconSizePx;
      public int iconTextSizePx;
      public int iconDrawablePaddingPx;
      public int iconDrawablePaddingOriginalPx;
  
      public int cellWidthPx;
      public int cellHeightPx;
      public int workspaceCellPaddingXPx;
  
      // Folder
      public int folderIconSizePx;
      public int folderIconOffsetYPx;
  
      // Folder cell
      public int folderCellWidthPx;
      public int folderCellHeightPx;
  
      // Folder child
      public int folderChildIconSizePx;
      public int folderChildTextSizePx;
      public int folderChildDrawablePaddingPx;
  
      // Hotseat
      public int hotseatCellHeightPx;
      // In portrait: size = height, in landscape: size = width
      public int hotseatBarSizePx;
      public int hotseatBarTopPaddingPx;
      public int hotseatBarBottomPaddingPx;
      // Start is the side next to the nav bar, end is the side next to the workspace
      public final int hotseatBarSidePaddingStartPx;
      public final int hotseatBarSidePaddingEndPx;
  
      // All apps
      public int allAppsCellHeightPx;
      public int allAppsCellWidthPx;
      public int allAppsIconSizePx;
      public int allAppsIconDrawablePaddingPx;
      public float allAppsIconTextSizePx;
  
      // Widgets
      public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
  
      // Drop Target
      public int dropTargetBarSizePx;
  
      // Insets
      private final Rect mInsets = new Rect();
      public final Rect workspacePadding = new Rect();
      private final Rect mHotseatPadding = new Rect();
      // When true, nav bar is on the left side of the screen.
      private boolean mIsSeascape;
  
      // Notification dots
      public DotRenderer mDotRendererWorkSpace;
      public DotRenderer mDotRendererAllApps;
  
      public DeviceProfile(Context context, InvariantDeviceProfile inv,
              InvariantDeviceProfile originalIDP, Point minSize, Point maxSize,
              int width, int height, boolean isLandscape, boolean isMultiWindowMode) {
  
          this.inv = inv;
          this.originalIdp = inv;
          this.isLandscape = isLandscape;
          this.isMultiWindowMode = isMultiWindowMode;
  
          // Determine sizes.
          widthPx = width;
          heightPx = height;
          if (isLandscape) {
              availableWidthPx = maxSize.x;
              availableHeightPx = minSize.y;
          } else {
              availableWidthPx = minSize.x;
              availableHeightPx = maxSize.y;
          }
  
          Resources res = context.getResources();
          DisplayMetrics dm = res.getDisplayMetrics();
  
          // Constants from resources
          isTablet = res.getBoolean(R.bool.is_tablet);
          isLargeTablet = res.getBoolean(R.bool.is_large_tablet);
          isPhone = !isTablet && !isLargeTablet;
          aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
          boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
  
          // Some more constants
          transposeLayoutWithOrientation =
                  res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
  
          context = getContext(context, isVerticalBarLayout()
                  ? Configuration.ORIENTATION_LANDSCAPE
                  : Configuration.ORIENTATION_PORTRAIT);
          res = context.getResources();
  
          edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
          desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
  
          int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
                  ? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
          int cellLayoutPadding = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
          if (isLandscape) {
              cellLayoutPaddingLeftRightPx = 0;
              cellLayoutBottomPaddingPx = cellLayoutPadding;
          } else {
              cellLayoutPaddingLeftRightPx = cellLayoutPaddingLeftRightMultiplier * cellLayoutPadding;
              cellLayoutBottomPaddingPx = 0;
          }
  
          verticalDragHandleSizePx = res.getDimensionPixelSize(
                  R.dimen.vertical_drag_handle_size);
          verticalDragHandleOverlapWorkspace =
                  res.getDimensionPixelSize(R.dimen.vertical_drag_handle_overlap_workspace);
  
          iconDrawablePaddingOriginalPx =
                  res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
          dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
          workspaceSpringLoadedBottomSpace =
                  res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
  
          workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
  
          hotseatBarTopPaddingPx =
                  res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
          hotseatBarBottomPaddingPx = (isTallDevice ? 0
                  : res.getDimensionPixelSize(FeatureFlags.HOTSEAT_WIDGET
                          ? R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding_widget
                          : R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding))
                  + res.getDimensionPixelSize(FeatureFlags.HOTSEAT_WIDGET
                          ? R.dimen.dynamic_grid_hotseat_bottom_padding_widget
                          : R.dimen.dynamic_grid_hotseat_bottom_padding);
          hotseatBarSidePaddingEndPx =
                  res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
          // Add a bit of space between nav bar and hotseat in vertical bar layout.
          hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? verticalDragHandleSizePx : 0;
          hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, dm) + (isVerticalBarLayout()
                  ? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx)
                  : (res.getDimensionPixelSize(FeatureFlags.HOTSEAT_WIDGET
                          ? R.dimen.dynamic_grid_hotseat_extra_vertical_size_widget
                          : R.dimen.dynamic_grid_hotseat_extra_vertical_size)
                          + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx));
  
          // Calculate all of the remaining variables.
          updateAvailableDimensions(dm, res);
  
          // Now that we have all of the variables calculated, we can tune certain sizes.
          if (!isVerticalBarLayout() && isPhone && isTallDevice) {
              // We increase the hotseat size when there is extra space.
              // ie. For a display with a large aspect ratio, we can keep the icons on the workspace
              // in portrait mode closer together by adding more height to the hotseat.
              // Note: This calculation was created after noticing a pattern in the design spec.
              int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
                      - verticalDragHandleSizePx;
              hotseatBarSizePx += extraSpace;
              if (!FeatureFlags.HOTSEAT_WIDGET) {
                  // Set an upper bound on bottom padding for very tall devices.
                  int maxExtraSpace = Math.round((hotseatBarSizePx - extraSpace) * 0.67f);
                  if (extraSpace > maxExtraSpace) {
                      hotseatBarTopPaddingPx += extraSpace - maxExtraSpace;
                      extraSpace = maxExtraSpace;
                  }
              }
              hotseatBarBottomPaddingPx += extraSpace;
  
              // Recalculate the available dimensions using the new hotseat size.
              updateAvailableDimensions(dm, res);
          }
  
          if (originalIDP != null && FeatureFlags.MAINTAIN_DRAWER_GRID) {
              // Grid size change should not affect All Apps UI, so we use the original profile
              // measurements here.
              DeviceProfile originalProfile = isLandscape
                      ? originalIDP.landscapeProfile
                      : originalIDP.portraitProfile;
              allAppsIconSizePx = originalProfile.iconSizePx;
              allAppsIconTextSizePx = originalProfile.iconTextSizePx;
              allAppsCellHeightPx = originalProfile.allAppsCellHeightPx;
              allAppsIconDrawablePaddingPx = originalProfile.iconDrawablePaddingOriginalPx;
              allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
          }
          updateWorkspacePadding();
  
          // This is done last, after iconSizePx is calculated above.
          mDotRendererWorkSpace = new DotRenderer(iconSizePx, IconShape.getShapePath(),
                  IconShape.DEFAULT_PATH_SIZE);
          mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace :
                  new DotRenderer(allAppsIconSizePx, IconShape.getShapePath(),
                          IconShape.DEFAULT_PATH_SIZE);
      }
  
      public DeviceProfile copy(Context context) {
          Point size = new Point(availableWidthPx, availableHeightPx);
          return new DeviceProfile(context, inv, originalIdp, size, size, widthPx, heightPx,
                  isLandscape, isMultiWindowMode);
      }
  
      public DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
          // We take the minimum sizes of this profile and it's multi-window variant to ensure that
          // the system decor is always excluded.
          mwSize.set(Math.min(availableWidthPx, mwSize.x), Math.min(availableHeightPx, mwSize.y));
  
          // In multi-window mode, we can have widthPx = availableWidthPx
          // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
          // widthPx and heightPx values where it's needed.
          DeviceProfile profile = new DeviceProfile(context, inv, originalIdp, mwSize, mwSize,
                  mwSize.x, mwSize.y, isLandscape, true);
  
          // If there isn't enough vertical cell padding with the labels displayed, hide the labels.
          float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx
                  - iconDrawablePaddingPx - profile.iconTextSizePx;
          if (workspaceCellPaddingY < profile.iconDrawablePaddingPx * 2) {
              profile.adjustToHideWorkspaceLabels();
          }
  
          // We use these scales to measure and layout the widgets using their full invariant profile
          // sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
          float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
          float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
          profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
          profile.updateWorkspacePadding();
  
          return profile;
      }
  
      /**
       * Inverse of {@link #getMultiWindowProfile(Context, Point)}
       * @return device profile corresponding to the current orientation in non multi-window mode.
       */
      public DeviceProfile getFullScreenProfile() {
          return isLandscape ? inv.landscapeProfile : inv.portraitProfile;
      }
  
      /**
       * Adjusts the profile so that the labels on the Workspace are hidden.
       * It is important to call this method after the All Apps variables have been set.
       */
      private void adjustToHideWorkspaceLabels() {
          iconTextSizePx = 0;
          iconDrawablePaddingPx = 0;
          cellHeightPx = iconSizePx;
  
          // In normal cases, All Apps cell height should equal the Workspace cell height.
          // Since we are removing labels from the Workspace, we need to manually compute the
          // All Apps cell height.
          int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1);
          allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
                  + Utilities.calculateTextHeight(allAppsIconTextSizePx)
                  + topBottomPadding * 2;
      }
  
      private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
          updateIconSize(1f, res, dm);
  
          // Check to see if the icons fit within the available height.  If not, then scale down.
          float usedHeight = (cellHeightPx * inv.numRows);
          int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
          if (usedHeight > maxHeight) {
              float scale = maxHeight / usedHeight;
              updateIconSize(scale, res, dm);
          }
          updateAvailableFolderCellDimensions(dm, res);
      }
  
      /**
       * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx,
       * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
       * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
       */
      private void updateIconSize(float scale, Resources res, DisplayMetrics dm) {
          // Workspace
          final boolean isVerticalLayout = isVerticalBarLayout();
          float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
          iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, dm) * scale));
          iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
          iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
  
          cellHeightPx = iconSizePx + iconDrawablePaddingPx
                  + Utilities.calculateTextHeight(iconTextSizePx);
          int cellYPadding = (getCellSize().y - cellHeightPx) / 2;
          if (iconDrawablePaddingPx > cellYPadding && !isVerticalLayout
                  && !isMultiWindowMode) {
              // Ensures that the label is closer to its corresponding icon. This is not an issue
              // with vertical bar layout or multi-window mode since the issue is handled separately
              // with their calls to {@link #adjustToHideWorkspaceLabels}.
              cellHeightPx -= (iconDrawablePaddingPx - cellYPadding);
              iconDrawablePaddingPx = cellYPadding;
          }
          cellWidthPx = iconSizePx + iconDrawablePaddingPx;
  
          allAppsIconSizePx = iconSizePx;
          allAppsIconTextSizePx = iconTextSizePx;
          allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
          allAppsCellHeightPx = getCellSize().y;
          allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
  
          if (isVerticalBarLayout()) {
              // Always hide the Workspace text with vertical bar layout.
              adjustToHideWorkspaceLabels();
          }
  
          // Hotseat
          if (isVerticalLayout) {
              hotseatBarSizePx = iconSizePx + hotseatBarSidePaddingStartPx
                      + hotseatBarSidePaddingEndPx;
          }
          hotseatCellHeightPx = iconSizePx;
  
          if (!isVerticalLayout) {
              int expectedWorkspaceHeight = availableHeightPx - hotseatBarSizePx
                      - verticalDragHandleSizePx - edgeMarginPx;
              float minRequiredHeight = dropTargetBarSizePx + workspaceSpringLoadedBottomSpace;
              workspaceSpringLoadShrinkFactor = Math.min(
                      res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f,
                      1 - (minRequiredHeight / expectedWorkspaceHeight));
          } else {
              workspaceSpringLoadShrinkFactor =
                      res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
          }
  
          // Folder icon
          folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
          folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
      }
  
      private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) {
          int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
                  + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
                  + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
  
          updateFolderCellSize(1f, dm, res);
  
          // Don't let the folder get too close to the edges of the screen.
          int folderMargin = edgeMarginPx * 2;
          Point totalWorkspacePadding = getTotalWorkspacePadding();
  
          // Check if the icons fit within the available height.
          float contentUsedHeight = folderCellHeightPx * inv.numFolderRows;
          int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize
                  - folderMargin;
          float scaleY = contentMaxHeight / contentUsedHeight;
  
          // Check if the icons fit within the available width.
          float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns;
          int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
          float scaleX = contentMaxWidth / contentUsedWidth;
  
          float scale = Math.min(scaleX, scaleY);
          if (scale < 1f) {
              updateFolderCellSize(scale, dm, res);
          }
      }
  
      private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) {
          folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, dm) * scale);
          folderChildTextSizePx =
                  (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
  
          int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
          int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale);
          int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale);
  
          folderCellWidthPx = folderChildIconSizePx + 2 * cellPaddingX;
          folderCellHeightPx = folderChildIconSizePx + 2 * cellPaddingY + textHeight;
          folderChildDrawablePaddingPx = Math.max(0,
                  (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
      }
  
      public void updateInsets(Rect insets) {
          mInsets.set(insets);
          updateWorkspacePadding();
      }
  
      /**
       * The current device insets. This is generally same as the insets being dispatched to
       * {@link Insettable} elements, but can differ if the element is using a different profile.
       */
      public Rect getInsets() {
          return mInsets;
      }
  
      public Point getCellSize() {
          return getCellSize(inv.numColumns, inv.numRows);
      }
  
      private Point getCellSize(int numColumns, int numRows) {
          Point result = new Point();
          // Since we are only concerned with the overall padding, layout direction does
          // not matter.
          Point padding = getTotalWorkspacePadding();
          result.x = calculateCellWidth(availableWidthPx - padding.x
                  - cellLayoutPaddingLeftRightPx * 2, numColumns);
          result.y = calculateCellHeight(availableHeightPx - padding.y
                  - cellLayoutBottomPaddingPx, numRows);
          return result;
      }
  
      public Point getTotalWorkspacePadding() {
          updateWorkspacePadding();
          return new Point(workspacePadding.left + workspacePadding.right,
                  workspacePadding.top + workspacePadding.bottom);
      }
  
      /**
       * Updates {@link #workspacePadding} as a result of any internal value change to reflect the
       * new workspace padding
       */
      private void updateWorkspacePadding() {
          Rect padding = workspacePadding;
          if (isVerticalBarLayout()) {
              padding.top = 0;
              padding.bottom = edgeMarginPx;
              if (isSeascape()) {
                  padding.left = hotseatBarSizePx;
                  padding.right = verticalDragHandleSizePx;
              } else {
                  padding.left = verticalDragHandleSizePx;
                  padding.right = hotseatBarSizePx;
              }
          } else {
              int paddingBottom = hotseatBarSizePx + verticalDragHandleSizePx
                      - verticalDragHandleOverlapWorkspace;
              if (isTablet) {
                  // Pad the left and right of the workspace to ensure consistent spacing
                  // between all icons
                  // The amount of screen space available for left/right padding.
                  int availablePaddingX = Math.max(0, widthPx - ((inv.numColumns * cellWidthPx) +
                          ((inv.numColumns - 1) * cellWidthPx)));
                  availablePaddingX = (int) Math.min(availablePaddingX,
                          widthPx * MAX_HORIZONTAL_PADDING_PERCENT);
                  int availablePaddingY = Math.max(0, heightPx - edgeMarginPx - paddingBottom
                          - (2 * inv.numRows * cellHeightPx) - hotseatBarTopPaddingPx
                          - hotseatBarBottomPaddingPx);
                  padding.set(availablePaddingX / 2, edgeMarginPx + availablePaddingY / 2,
                          availablePaddingX / 2, paddingBottom + availablePaddingY / 2);
              } else {
                  // Pad the top and bottom of the workspace with search/hotseat bar sizes
                  padding.set(desiredWorkspaceLeftRightMarginPx,
                          edgeMarginPx,
                          desiredWorkspaceLeftRightMarginPx,
                          paddingBottom);
              }
          }
      }
  
      public Rect getHotseatLayoutPadding() {
          if (isVerticalBarLayout()) {
              if (isSeascape()) {
                  mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx,
                          mInsets.top, hotseatBarSidePaddingEndPx, mInsets.bottom);
              } else {
                  mHotseatPadding.set(hotseatBarSidePaddingEndPx, mInsets.top,
                          mInsets.right + hotseatBarSidePaddingStartPx, mInsets.bottom);
              }
          } else {
  
              // We want the edges of the hotseat to line up with the edges of the workspace, but the
              // icons in the hotseat are a different size, and so don't line up perfectly. To account
              // for this, we pad the left and right of the hotseat with half of the difference of a
              // workspace cell vs a hotseat cell.
              float workspaceCellWidth = (float) widthPx / inv.numColumns;
              float hotseatCellWidth = (float) widthPx / inv.numHotseatIcons;
              int hotseatAdjustment = Math.round((workspaceCellWidth - hotseatCellWidth) / 2);
              mHotseatPadding.set(
                      hotseatAdjustment + workspacePadding.left + cellLayoutPaddingLeftRightPx,
                      hotseatBarTopPaddingPx,
                      hotseatAdjustment + workspacePadding.right + cellLayoutPaddingLeftRightPx,
                      hotseatBarBottomPaddingPx + mInsets.bottom + cellLayoutBottomPaddingPx);
          }
          return mHotseatPadding;
      }
  
      /**
       * @return the bounds for which the open folders should be contained within
       */
      public Rect getAbsoluteOpenFolderBounds() {
          if (isVerticalBarLayout()) {
              // Folders should only appear right of the drop target bar and left of the hotseat
              return new Rect(mInsets.left + dropTargetBarSizePx + edgeMarginPx,
                      mInsets.top,
                      mInsets.left + availableWidthPx - hotseatBarSizePx - edgeMarginPx,
                      mInsets.top + availableHeightPx);
          } else {
              // Folders should only appear below the drop target bar and above the hotseat
              return new Rect(mInsets.left + edgeMarginPx,
                      mInsets.top + dropTargetBarSizePx + edgeMarginPx,
                      mInsets.left + availableWidthPx - edgeMarginPx,
                      mInsets.top + availableHeightPx - hotseatBarSizePx
                              - verticalDragHandleSizePx - edgeMarginPx);
          }
      }
  
      public static int calculateCellWidth(int width, int countX) {
          return width / countX;
      }
      public static int calculateCellHeight(int height, int countY) {
          return height / countY;
      }
  
      /**
       * When {@code true}, the device is in landscape mode and the hotseat is on the right column.
       * When {@code false}, either device is in portrait mode or the device is in landscape mode and
       * the hotseat is on the bottom row.
       */
      public boolean isVerticalBarLayout() {
          return isLandscape && transposeLayoutWithOrientation;
      }
  
      /**
       * Returns true when the number of workspace columns and all apps columns differs.
       */
      private boolean allAppsHasDifferentNumColumns() {
          return inv.numAllAppsColumns != inv.numColumns;
      }
  
      /**
       * Updates orientation information and returns true if it has changed from the previous value.
       */
      public boolean updateIsSeascape(Context context) {
          if (isVerticalBarLayout()) {
              boolean isSeascape = DefaultDisplay.INSTANCE.get(context).getInfo().rotation
                      == Surface.ROTATION_270;
              if (mIsSeascape != isSeascape) {
                  mIsSeascape = isSeascape;
                  return true;
              }
          }
          return false;
      }
  
      public boolean isSeascape() {
          return isVerticalBarLayout() && mIsSeascape;
      }
  
      public boolean shouldFadeAdjacentWorkspaceScreens() {
          return isVerticalBarLayout() || isLargeTablet;
      }
  
      public int getCellHeight(@ContainerType int containerType) {
          switch (containerType) {
              case CellLayout.WORKSPACE:
                  return cellHeightPx;
              case CellLayout.FOLDER:
                  return folderCellHeightPx;
              case CellLayout.HOTSEAT:
                  return hotseatCellHeightPx;
              default:
                  // ??
                  return 0;
          }
      }
  
      private static Context getContext(Context c, int orientation) {
          Configuration context = new Configuration(c.getResources().getConfiguration());
          context.orientation = orientation;
          return c.createConfigurationContext(context);
      }
  
      /**
       * Callback when a component changes the DeviceProfile associated with it, as a result of
       * configuration change
       */
      public interface OnDeviceProfileChangeListener {
  
          /**
           * Called when the device profile is reassigned. Note that for layout and measurements, it
           * is sufficient to listen for inset changes. Use this callback when you need to perform
           * a one time operation.
           */
          void onDeviceProfileChanged(DeviceProfile dp);
      }
  }
  