Calculate hotseat width based on available width

Navigation buttons take space on the hotseat, so hotseat width (qsb width and border space between icons) should be calculated instead of having a coded value.

Bug: 223724516
Bug: 228998463
Test: manual, changing display size
Test: manual, changing hotseatBarEndOffset value
Test: HotseatWidthCalculationTest
Change-Id: Ibd4f5ff2e06afda8e7420fb744db049d2e418e14
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 0d41230..f27eb79 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -158,7 +158,7 @@
     public int folderChildDrawablePaddingPx;
 
     // Hotseat
-    public final int numShownHotseatIcons;
+    public int numShownHotseatIcons;
     public int hotseatCellHeightPx;
     public final boolean areNavButtonsInline;
     // In portrait: size = height, in landscape: size = width
@@ -362,15 +362,9 @@
                 && hotseatQsbHeight > 0;
         isQsbInline = inv.inlineQsb[mTypeIndex] && canQsbInline;
 
-        // We shrink hotseat sizes regardless of orientation, if nav buttons are inline and QSB
-        // might be inline in either orientations, to keep hotseat size consistent across rotation.
         areNavButtonsInline = isTaskbarPresent && !isGestureMode;
-        if (areNavButtonsInline && canQsbInline) {
-            numShownHotseatIcons = inv.numShrunkenHotseatIcons;
-        } else {
-            numShownHotseatIcons =
-                    isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons;
-        }
+        numShownHotseatIcons =
+                isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons;
 
         numShownAllAppsColumns =
                 isTwoPanels ? inv.numDatabaseAllAppsColumns : inv.numAllAppsColumns;
@@ -460,8 +454,7 @@
         updateWorkspacePadding();
 
         // Hotseat and QSB width depends on updated cellSize and workspace padding
-        hotseatBorderSpace = calculateHotseatBorderSpace();
-        hotseatQsbWidth = calculateQsbWidth();
+        recalculateHotseatWidthAndBorderSpace(res);
 
         // AllApps height calculation depends on updated cellSize
         if (isTablet) {
@@ -499,7 +492,7 @@
      * QSB width is always calculated because when in 3 button nav the width doesn't follow the
      * width of the hotseat.
      */
-    private int calculateQsbWidth() {
+    private int calculateQsbWidth(int hotseatBorderSpace) {
         if (isQsbInline) {
             int columns = getPanelCount() * inv.numColumns;
             return getIconToIconWidthForColumns(columns)
@@ -546,6 +539,70 @@
         }
     }
 
+    private void recalculateHotseatWidthAndBorderSpace(Resources res) {
+        hotseatBorderSpace = calculateHotseatBorderSpace();
+        hotseatQsbWidth = calculateQsbWidth(hotseatBorderSpace);
+        // Spaces should be correct when there nav buttons are not inline
+        if (!areNavButtonsInline) {
+            return;
+        }
+
+        // Get the maximum width that the hotseat can be
+        int columns = getPanelCount() * inv.numColumns;
+        int maxHotseatWidth = getIconToIconWidthForColumns(columns);
+        int sideSpace = (availableWidthPx - maxHotseatWidth) / 2;
+        int inlineButtonsOverlap = Math.max(0, hotseatBarEndOffset - sideSpace);
+        // decrease how much the nav buttons go "inside" the hotseat
+        maxHotseatWidth -= inlineButtonsOverlap;
+
+        // Get how much space is required to show the hotseat with QSB
+        int requiredWidth = getHotseatRequiredWidth();
+
+        // If spaces are fine, use them
+        if (requiredWidth <= maxHotseatWidth) {
+            return;
+        }
+
+        // Calculate the difference of widths and remove a little from each space between icons
+        // and QSB if it's inline
+        int spaceDiff = requiredWidth - maxHotseatWidth;
+        int numOfSpaces = numShownHotseatIcons - (isQsbInline ? 0 : 1);
+        hotseatBorderSpace -= (spaceDiff / numOfSpaces);
+
+        int minHotseatIconSpaceDp = res.getDimensionPixelSize(R.dimen.min_hotseat_icon_space);
+        int minHotseatQsbWidthDp = res.getDimensionPixelSize(R.dimen.min_hotseat_qsb_width);
+
+        if (hotseatBorderSpace >= minHotseatIconSpaceDp) {
+            return;
+        }
+
+        // Border space can't be less than the minimum
+        hotseatBorderSpace = minHotseatIconSpaceDp;
+        requiredWidth = getHotseatRequiredWidth();
+
+        // If there is an inline qsb, change its size
+        if (isQsbInline) {
+            hotseatQsbWidth -= requiredWidth - maxHotseatWidth;
+            if (hotseatQsbWidth >= minHotseatQsbWidthDp) {
+                return;
+            }
+
+            // QSB can't be less than the minimum
+            hotseatQsbWidth = minHotseatQsbWidthDp;
+        }
+
+        // If it still doesn't fit, start removing icons
+        do {
+            numShownHotseatIcons--;
+            requiredWidth = getHotseatRequiredWidth();
+        } while (requiredWidth > maxHotseatWidth && numShownHotseatIcons > 1);
+
+        // Add back some space between the icons
+        spaceDiff = maxHotseatWidth - requiredWidth;
+        numOfSpaces = numShownHotseatIcons - (isQsbInline ? 0 : 1);
+        hotseatBorderSpace += (spaceDiff / numOfSpaces);
+    }
+
     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
         return getCellLayoutBorderSpace(idp, 1f);
     }
@@ -778,15 +835,10 @@
      */
     private int calculateHotseatBorderSpace() {
         if (!isScalableGrid) return 0;
-        //TODO(http://b/228998082) remove this when 3 button spaces are fixed
-        if (areNavButtonsInline) {
-            return pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics);
-        } else {
-            int columns = inv.hotseatColumnSpan[mTypeIndex];
-            float hotseatWidthPx = getIconToIconWidthForColumns(columns);
-            float hotseatIconsTotalPx = iconSizePx * numShownHotseatIcons;
-            return (int) (hotseatWidthPx - hotseatIconsTotalPx) / (numShownHotseatIcons - 1);
-        }
+        int columns = inv.hotseatColumnSpan[mTypeIndex];
+        float hotseatWidthPx = getIconToIconWidthForColumns(columns);
+        float hotseatIconsTotalPx = iconSizePx * numShownHotseatIcons;
+        return (int) (hotseatWidthPx - hotseatIconsTotalPx) / (numShownHotseatIcons - 1);
     }
 
 
@@ -1078,10 +1130,7 @@
                     hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx;
 
             // Push icons to the side
-            int additionalQsbSpace = isQsbInline ? hotseatQsbWidth + hotseatBorderSpace : 0;
-            int requiredWidth = iconSizePx * numShownHotseatIcons
-                    + hotseatBorderSpace * (numShownHotseatIcons - 1)
-                    + additionalQsbSpace;
+            int requiredWidth = getHotseatRequiredWidth();
             int hotseatWidth = Math.min(requiredWidth, availableWidthPx - hotseatBarEndOffset);
             int sideSpacing = (availableWidthPx - hotseatWidth) / 2;
 
@@ -1090,9 +1139,9 @@
 
             boolean isRtl = Utilities.isRtl(context.getResources());
             if (isRtl) {
-                hotseatBarPadding.right += additionalQsbSpace;
+                hotseatBarPadding.right += getAdditionalQsbSpace();
             } else {
-                hotseatBarPadding.left += additionalQsbSpace;
+                hotseatBarPadding.left += getAdditionalQsbSpace();
             }
 
             if (hotseatBarEndOffset > sideSpacing) {
@@ -1127,6 +1176,20 @@
         return hotseatBarPadding;
     }
 
+    private int getAdditionalQsbSpace() {
+        return isQsbInline ? hotseatQsbWidth + hotseatBorderSpace : 0;
+    }
+
+    /**
+     * Calculate how much space the hotseat needs to be shown completely
+     */
+    private int getHotseatRequiredWidth() {
+        int additionalQsbSpace = getAdditionalQsbSpace();
+        return iconSizePx * numShownHotseatIcons
+                + hotseatBorderSpace * (numShownHotseatIcons - 1)
+                + additionalQsbSpace;
+    }
+
     /**
      * Returns the number of pixels the QSB is translated from the bottom of the screen.
      */