Set min aspect ratio for unresizable apps to 3:2.

Also, allow to override it via a config overlay.

Bug: 230757107
Test: atest WmTests:SizeCompatTests
Change-Id: I750b8fd464b041f16bf18c3bdbfb1a28a2132902
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3acd171..66689ca 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5176,6 +5176,12 @@
     <!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. -->
     <bool name="config_letterboxIsEducationEnabled">false</bool>
 
+    <!-- Default min aspect ratio for unresizable apps which is used when an app doesn't specify
+         android:minAspectRatio in accordance with CDD 7.1.1.2 requirement:
+         https://source.android.com/compatibility/12/android-12-cdd#7112_screen_aspect_ratio.
+         An exception will be thrown if the given aspect ratio < 4:3.  -->
+    <item name="config_letterboxDefaultMinAspectRatioForUnresizableApps" format="float" type="dimen">1.5</item>
+
     <!-- Whether a camera compat controller is enabled to allow the user to apply or revert
          treatment for stretched issues in camera viewfinder. -->
     <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3c03b41..49be0b8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4395,6 +4395,7 @@
   <java-symbol type="integer" name="config_letterboxDefaultPositionForHorizontalReachability" />
   <java-symbol type="integer" name="config_letterboxDefaultPositionForVerticalReachability" />
   <java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
+  <java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
   <java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
 
   <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e700103..328f55f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7860,7 +7860,6 @@
         // Vertical position
         int offsetY = 0;
         if (parentBounds.height() != screenResolvedBounds.height()) {
-
             if (screenResolvedBounds.height() >= parentAppBounds.height()) {
                 // If resolved bounds overlap with insets, center within app bounds.
                 offsetY = getCenterOffset(
@@ -7908,6 +7907,10 @@
         return mLetterboxBoundsForFixedOrientationAndAspectRatio != null;
     }
 
+    boolean isAspectRatioApplied() {
+        return mIsAspectRatioApplied;
+    }
+
     /**
      * Whether this activity is eligible for letterbox eduction.
      *
@@ -8632,7 +8635,18 @@
      * Returns the min aspect ratio of this activity.
      */
     private float getMinAspectRatio() {
-        return info.getMinAspectRatio(getRequestedOrientation());
+        float infoAspectRatio = info.getMinAspectRatio(getRequestedOrientation());
+        // Complying with the CDD 7.1.1.2 requirement for unresizble apps:
+        // https://source.android.com/compatibility/12/android-12-cdd#7112_screen_aspect_ratio
+        return infoAspectRatio < 1f && info.resizeMode == RESIZE_MODE_UNRESIZEABLE
+                    // TODO(233582832): Consider removing fixed-orientation condition.
+                    // Some apps switching from tablet to phone layout at the certain size
+                    // threshold. This may lead to flickering on tablets in landscape orientation
+                    // if an app sets orientation to portrait dynamically because of aspect ratio
+                    // restriction applied here.
+                    && getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
+                ? mWmService.mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
+                : infoAspectRatio;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 2c7540b..b931f79 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -37,6 +37,11 @@
      */
     static final float MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO = 1.0f;
 
+    // Min allowed aspect ratio for unresizable apps which is used when an app doesn't specify
+    // android:minAspectRatio in accordance with the CDD 7.1.1.2 requirement:
+    // https://source.android.com/compatibility/12/android-12-cdd#7112_screen_aspect_ratio
+    static final float MIN_UNRESIZABLE_ASPECT_RATIO = 4 / 3f;
+
     /** Enum for Letterbox background type. */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({LETTERBOX_BACKGROUND_SOLID_COLOR, LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND,
@@ -104,6 +109,11 @@
     // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored.
     private float mFixedOrientationLetterboxAspectRatio;
 
+    // Default min aspect ratio for unresizable apps which is used when an app doesn't specify
+    // android:minAspectRatio in accordance with the CDD 7.1.1.2 requirement:
+    // https://source.android.com/compatibility/12/android-12-cdd#7112_screen_aspect_ratio
+    private float mDefaultMinAspectRatioForUnresizableApps;
+
     // Corners radius for activities presented in the letterbox mode, values < 0 will be ignored.
     private int mLetterboxActivityCornersRadius;
 
@@ -204,6 +214,8 @@
         mLetterboxPositionForVerticalReachability = mDefaultPositionForVerticalReachability;
         mIsEducationEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsEducationEnabled);
+        setDefaultMinAspectRatioForUnresizableApps(mContext.getResources().getFloat(
+                R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
     }
 
     /**
@@ -233,6 +245,43 @@
     }
 
     /**
+     * Resets the min aspect ratio for unresizable apps which is used when an app doesn't specify
+     * {@code android:minAspectRatio} to {@link
+     * R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps}.
+     *
+     * @throws AssertionError if {@link
+     * R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps} is < {@link
+     * #MIN_UNRESIZABLE_ASPECT_RATIO}.
+     */
+    void resetDefaultMinAspectRatioForUnresizableApps() {
+        setDefaultMinAspectRatioForUnresizableApps(mContext.getResources().getFloat(
+                R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
+    }
+
+    /**
+     * Gets the min aspect ratio for unresizable apps which is used when an app doesn't specify
+     * {@code android:minAspectRatio}.
+     */
+    float getDefaultMinAspectRatioForUnresizableApps() {
+        return mDefaultMinAspectRatioForUnresizableApps;
+    }
+
+    /**
+     * Overrides the min aspect ratio for unresizable apps which is used when an app doesn't
+     * specify {@code android:minAspectRatio}.
+     *
+     * @throws AssertionError if given value is < {@link #MIN_UNRESIZABLE_ASPECT_RATIO}.
+     */
+    void setDefaultMinAspectRatioForUnresizableApps(float aspectRatio) {
+        if (aspectRatio < MIN_UNRESIZABLE_ASPECT_RATIO) {
+            throw new AssertionError(
+                    "Unexpected min aspect ratio for unresizable apps, it should be <= "
+                            + MIN_UNRESIZABLE_ASPECT_RATIO + " but was " + aspectRatio);
+        }
+        mDefaultMinAspectRatioForUnresizableApps = aspectRatio;
+    }
+
+    /**
      * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0,
      * both it and a value of {@link
      * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 2015206..9dbc477 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -540,6 +540,8 @@
         pw.println(prefix + "  fixedOrientationLetterboxAspectRatio="
                 + getFixedOrientationLetterboxAspectRatio(
                         mActivityRecord.getParent().getConfiguration()));
+        pw.println(prefix + "  defaultMinAspectRatioForUnresizableApps="
+                + mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps());
     }
 
     /**
@@ -556,6 +558,9 @@
         if (mainWin.isLetterboxedForDisplayCutout()) {
             return "DISPLAY_CUTOUT";
         }
+        if (mActivityRecord.isAspectRatioApplied()) {
+            return "ASPECT_RATIO";
+        }
         return "UNKNOWN_REASON";
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 8a6a4df..79c6ee7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -629,6 +629,26 @@
         return 0;
     }
 
+    private int runSetDefaultMinAspectRatioForUnresizableApps(PrintWriter pw)
+            throws RemoteException {
+        final float aspectRatio;
+        try {
+            String arg = getNextArgRequired();
+            aspectRatio = Float.parseFloat(arg);
+        } catch (NumberFormatException  e) {
+            getErrPrintWriter().println("Error: bad aspect ratio format " + e);
+            return -1;
+        } catch (IllegalArgumentException  e) {
+            getErrPrintWriter().println(
+                    "Error: aspect ratio should be provided as an argument " + e);
+            return -1;
+        }
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setDefaultMinAspectRatioForUnresizableApps(aspectRatio);
+        }
+        return 0;
+    }
+
     private int runSetLetterboxActivityCornersRadius(PrintWriter pw) throws RemoteException {
         final int cornersRadius;
         try {
@@ -939,6 +959,9 @@
                 case "--aspectRatio":
                     runSetFixedOrientationLetterboxAspectRatio(pw);
                     break;
+                case "--minAspectRatioForUnresizable":
+                    runSetDefaultMinAspectRatioForUnresizableApps(pw);
+                    break;
                 case "--cornerRadius":
                     runSetLetterboxActivityCornersRadius(pw);
                     break;
@@ -998,6 +1021,9 @@
                     case "aspectRatio":
                         mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
                         break;
+                    case "minAspectRatioForUnresizable":
+                        mLetterboxConfiguration.resetDefaultMinAspectRatioForUnresizableApps();
+                        break;
                     case "cornerRadius":
                         mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
                         break;
@@ -1121,6 +1147,7 @@
     private void resetLetterboxStyle() {
         synchronized (mInternal.mGlobalLock) {
             mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
+            mLetterboxConfiguration.resetDefaultMinAspectRatioForUnresizableApps();
             mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
             mLetterboxConfiguration.resetLetterboxBackgroundType();
             mLetterboxConfiguration.resetLetterboxBackgroundColor();
@@ -1145,6 +1172,8 @@
                     + mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier());
             pw.println("Aspect ratio: "
                     + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
+            pw.println("Default min aspect ratio for unresizable apps: "
+                    + mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps());
             pw.println("Is horizontal reachability enabled: "
                     + mLetterboxConfiguration.getIsHorizontalReachabilityEnabled());
             pw.println("Is vertical reachability enabled: "
@@ -1261,6 +1290,11 @@
                 + LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
         pw.println("        both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will");
         pw.println("        be ignored and framework implementation will determine aspect ratio.");
+        pw.println("      --minAspectRatioForUnresizable aspectRatio");
+        pw.println("        Default min aspect ratio for unresizable apps which is used when an");
+        pw.println("        app doesn't specify android:minAspectRatio. An exception will be");
+        pw.println("        thrown if aspectRatio < "
+                + LetterboxConfiguration.MIN_UNRESIZABLE_ASPECT_RATIO);
         pw.println("      --cornerRadius radius");
         pw.println("        Corners radius for activities in the letterbox mode. If radius < 0,");
         pw.println("        both it and R.integer.config_letterboxActivityCornersRadius will be");
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index dbb7fae..db3a51c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -39,6 +39,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -168,7 +169,8 @@
         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
 
-        prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT);
+        prepareLimitedBounds(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT,
+                false /* isUnresizable */);
         final Rect dagBounds = new Rect(mFirstRoot.getBounds());
         final Rect taskBounds = new Rect(mFirstTask.getBounds());
         final Rect activityBounds = new Rect(mFirstActivity.getBounds());
@@ -209,8 +211,10 @@
         assertThat(activityConfigBounds.width()).isEqualTo(activityBounds.width());
         assertThat(activityConfigBounds.height()).isEqualTo(activityBounds.height());
         assertThat(activitySizeCompatBounds.height()).isEqualTo(newTaskBounds.height());
-        assertThat(activitySizeCompatBounds.width()).isEqualTo(
-                newTaskBounds.height() * newTaskBounds.height() / newTaskBounds.width());
+        final float defaultAspectRatio = mFirstActivity.mWmService.mLetterboxConfiguration
+                .getDefaultMinAspectRatioForUnresizableApps();
+        assertEquals(activitySizeCompatBounds.width(),
+                newTaskBounds.height() / defaultAspectRatio, 0.5);
     }
 
     @Test
@@ -230,8 +234,9 @@
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
         assertThat(taskBounds).isEqualTo(dagBounds);
         assertThat(activityBounds.width()).isEqualTo(dagBounds.width());
-        assertThat(activityBounds.height())
-                .isEqualTo(dagBounds.width() * dagBounds.width() / dagBounds.height());
+        final float defaultAspectRatio = mFirstActivity.mWmService.mLetterboxConfiguration
+                .getDefaultMinAspectRatioForUnresizableApps();
+        assertEquals(activityBounds.height(), dagBounds.width() / defaultAspectRatio, 0.5);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 0240cc3..624dcbc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1429,12 +1429,10 @@
         setUpDisplaySizeWithApp(2800, 1400);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
 
-        // Portrait fixed app with min aspect ratio higher that aspect ratio override for fixed
-        // orientation letterbox.
         final float fixedOrientationLetterboxAspectRatio = 1.1f;
         mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(
                 fixedOrientationLetterboxAspectRatio);
-        prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
+        prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ false);
 
         final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
         final Rect activityBounds = new Rect(mActivity.getBounds());
@@ -1455,6 +1453,37 @@
     }
 
     @Test
+    public void testDisplayIgnoreOrientationRequest_unresizableWithCorrespondingMinAspectRatio() {
+        // Set up a display in landscape and ignoring orientation request.
+        setUpDisplaySizeWithApp(2800, 1400);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        final float fixedOrientationLetterboxAspectRatio = 1.1f;
+        mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(
+                fixedOrientationLetterboxAspectRatio);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+        final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
+        final Rect activityBounds = new Rect(mActivity.getBounds());
+
+        // Display shouldn't be rotated.
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED,
+                mActivity.mDisplayContent.getLastOrientation());
+        assertTrue(displayBounds.width() > displayBounds.height());
+
+        // App should launch in fixed orientation letterbox.
+        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        assertFalse(mActivity.inSizeCompatMode());
+
+        // Letterbox logic should use config_letterboxDefaultMinAspectRatioForUnresizableApps over
+        // config_fixedOrientationLetterboxAspectRatio.
+        assertEquals(displayBounds.height(), activityBounds.height());
+        final float defaultAspectRatio = mActivity.mWmService.mLetterboxConfiguration
+                .getDefaultMinAspectRatioForUnresizableApps();
+        assertEquals(displayBounds.height() / defaultAspectRatio, activityBounds.width(), 0.5);
+    }
+
+    @Test
     public void
             testDisplayIgnoreOrientationRequest_orientationLetterboxBecameSizeCompatAfterRotate() {
         // Set up a display in landscape and ignoring orientation request.
@@ -1928,7 +1957,7 @@
     }
 
     @Test
-    public void testSupportsNonResizableInSplitScreen_fillTaskForSameOrientation() {
+    public void testSupportsNonResizableInSplitScreen_aspectRatioLetterboxInSameOrientation() {
         // Support non resizable in multi window
         mAtm.mDevEnableNonResizableMultiWindow = true;
         setUpDisplaySizeWithApp(1000, 2800);
@@ -1966,7 +1995,12 @@
         // Activity bounds fill split screen.
         final Rect primarySplitBounds = new Rect(organizer.mPrimary.getBounds());
         final Rect letterboxedBounds = new Rect(mActivity.getBounds());
-        assertEquals(primarySplitBounds, letterboxedBounds);
+        // Activity is letterboxed for aspect ratio.
+        assertEquals(primarySplitBounds.height(), letterboxedBounds.height());
+        final float defaultAspectRatio = mActivity.mWmService.mLetterboxConfiguration
+                .getDefaultMinAspectRatioForUnresizableApps();
+        assertEquals(primarySplitBounds.height() / defaultAspectRatio,
+                letterboxedBounds.width(), 0.5);
     }
 
     @Test