Merge "[25/n] Reduce LetterboxUiController-Reachability dependencies" into main
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index eea3ab8..7210098 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1988,8 +1988,8 @@
         // Don't move below setOrientation(info.screenOrientation) since it triggers
         // getOverrideOrientation that requires having mLetterboxUiController
         // initialised.
-        mLetterboxUiController = new LetterboxUiController(mWmService, this);
         mAppCompatController = new AppCompatController(mWmService, this);
+        mLetterboxUiController = new LetterboxUiController(mWmService, this);
         mResolveConfigHint = new TaskFragment.ConfigOverrideHint();
         if (mWmService.mFlags.mInsetsDecoupledConfiguration) {
             // When the stable configuration is the default behavior, override for the legacy apps
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
index e4e7654..90bfddb 100644
--- a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
@@ -28,10 +28,9 @@
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Rect;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import java.util.function.Supplier;
 
 /**
@@ -43,6 +42,8 @@
     private final ActivityRecord mActivityRecord;
     @NonNull
     private final AppCompatConfiguration mAppCompatConfiguration;
+    @Nullable
+    private Supplier<Rect> mLetterboxInnerBoundsSupplier;
 
     AppCompatReachabilityPolicy(@NonNull ActivityRecord activityRecord,
             @NonNull AppCompatConfiguration appCompatConfiguration) {
@@ -50,15 +51,34 @@
         mAppCompatConfiguration = appCompatConfiguration;
     }
 
-    @VisibleForTesting
-    void handleHorizontalDoubleTap(int x, @NonNull Supplier<Rect> innerFrameSupplier) {
+    /**
+     * To handle reachability a supplier for the current letterox inner bounds is required.
+     * <p/>
+     * @param letterboxInnerBoundsSupplier The supplier for the letterbox inner bounds.
+     */
+    void setLetterboxInnerBoundsSupplier(@Nullable Supplier<Rect> letterboxInnerBoundsSupplier) {
+        mLetterboxInnerBoundsSupplier = letterboxInnerBoundsSupplier;
+    }
+
+    /**
+     * Handles double tap events for reachability.
+     * <p/>
+     * @param x Double tap x coordinate.
+     * @param y Double tap y coordinate.
+     */
+    void handleDoubleTap(int x, int y) {
+        handleHorizontalDoubleTap(x);
+        handleVerticalDoubleTap(y);
+    }
+
+    private void handleHorizontalDoubleTap(int x) {
         final AppCompatReachabilityOverrides reachabilityOverrides =
                 mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
         if (!reachabilityOverrides.isHorizontalReachabilityEnabled()
                 || mActivityRecord.isInTransition()) {
             return;
         }
-        final Rect letterboxInnerFrame = innerFrameSupplier.get();
+        final Rect letterboxInnerFrame = getLetterboxInnerFrame();
         if (letterboxInnerFrame.left <= x && letterboxInnerFrame.right >= x) {
             // Only react to clicks at the sides of the letterboxed app window.
             return;
@@ -97,15 +117,14 @@
         mActivityRecord.recomputeConfiguration();
     }
 
-    @VisibleForTesting
-    void handleVerticalDoubleTap(int y, @NonNull Supplier<Rect> innerFrameSupplier) {
+    private void handleVerticalDoubleTap(int y) {
         final AppCompatReachabilityOverrides reachabilityOverrides =
                 mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
         if (!reachabilityOverrides.isVerticalReachabilityEnabled()
                 || mActivityRecord.isInTransition()) {
             return;
         }
-        final Rect letterboxInnerFrame = innerFrameSupplier.get();
+        final Rect letterboxInnerFrame = getLetterboxInnerFrame();
         if (letterboxInnerFrame.top <= y && letterboxInnerFrame.bottom >= y) {
             // Only react to clicks at the top and bottom of the letterboxed app window.
             return;
@@ -150,4 +169,10 @@
         mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
                 .logLetterboxPositionChange(mActivityRecord, letterboxPositionChangeForLog);
     }
+
+    @NonNull
+    private Rect getLetterboxInnerFrame() {
+        return mLetterboxInnerBoundsSupplier != null ? mLetterboxInnerBoundsSupplier.get()
+                : new Rect();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index d98c2b3..a5db904 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -63,7 +63,7 @@
     /**
      * Returns the aspect ratio of the given {@code rect}.
      */
-    static float computeAspectRatio(Rect rect) {
+    static float computeAspectRatio(@NonNull Rect rect) {
         final int width = rect.width();
         final int height = rect.height();
         if (width == 0 || height == 0) {
@@ -181,4 +181,30 @@
         appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top.mAppCompatController
                 .getAppCompatCameraOverrides().getFreeformCameraCompatMode();
     }
+
+    /**
+     * Returns a string representing the reason for letterboxing. This method assumes the activity
+     * is letterboxed.
+     * @param activityRecord The {@link ActivityRecord} for the letterboxed activity.
+     * @param mainWin   The {@link WindowState} used to letterboxing.
+     */
+    @NonNull
+    static String getLetterboxReasonString(@NonNull ActivityRecord activityRecord,
+            @NonNull WindowState mainWin) {
+        if (activityRecord.inSizeCompatMode()) {
+            return "SIZE_COMPAT_MODE";
+        }
+        final AppCompatAspectRatioPolicy aspectRatioPolicy = activityRecord.mAppCompatController
+                .getAppCompatAspectRatioPolicy();
+        if (aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio()) {
+            return "FIXED_ORIENTATION";
+        }
+        if (mainWin.isLetterboxedForDisplayCutout()) {
+            return "DISPLAY_CUTOUT";
+        }
+        if (aspectRatioPolicy.isLetterboxedForAspectRatioOnly()) {
+            return "ASPECT_RATIO";
+        }
+        return "UNKNOWN_REASON";
+    }
 }
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 2aa7c0c..3fc5eaf 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -40,7 +40,6 @@
 
 import java.util.function.BooleanSupplier;
 import java.util.function.DoubleSupplier;
-import java.util.function.IntConsumer;
 import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 
@@ -76,9 +75,8 @@
     // for overlaping an app window and letterbox surfaces.
     private final LetterboxSurface mFullWindowSurface = new LetterboxSurface("fullWindow");
     private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
-    // Reachability gestures.
-    private final IntConsumer mDoubleTapCallbackX;
-    private final IntConsumer mDoubleTapCallbackY;
+    @NonNull
+    private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy;
 
     /**
      * Constructs a Letterbox.
@@ -92,8 +90,7 @@
             BooleanSupplier hasWallpaperBackgroundSupplier,
             IntSupplier blurRadiusSupplier,
             DoubleSupplier darkScrimAlphaSupplier,
-            IntConsumer doubleTapCallbackX,
-            IntConsumer doubleTapCallbackY,
+            @NonNull AppCompatReachabilityPolicy appCompatReachabilityPolicy,
             Supplier<SurfaceControl> parentSurface) {
         mSurfaceControlFactory = surfaceControlFactory;
         mTransactionFactory = transactionFactory;
@@ -102,9 +99,10 @@
         mHasWallpaperBackgroundSupplier = hasWallpaperBackgroundSupplier;
         mBlurRadiusSupplier = blurRadiusSupplier;
         mDarkScrimAlphaSupplier = darkScrimAlphaSupplier;
-        mDoubleTapCallbackX = doubleTapCallbackX;
-        mDoubleTapCallbackY = doubleTapCallbackY;
+        mAppCompatReachabilityPolicy = appCompatReachabilityPolicy;
         mParentSurfaceSupplier = parentSurface;
+        // TODO Remove after Letterbox refactoring.
+        mAppCompatReachabilityPolicy.setLetterboxInnerBoundsSupplier(this::getInnerFrame);
     }
 
     /**
@@ -290,8 +288,8 @@
                 // This check prevents late events to be handled in case the Letterbox has been
                 // already destroyed and so mOuter.isEmpty() is true.
                 if (!mOuter.isEmpty() && e.getAction() == MotionEvent.ACTION_UP) {
-                    mDoubleTapCallbackX.accept((int) e.getRawX());
-                    mDoubleTapCallbackY.accept((int) e.getRawY());
+                    mAppCompatReachabilityPolicy.handleDoubleTap((int) e.getRawX(),
+                            (int) e.getRawY());
                     return true;
                 }
                 return false;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index eb8a637..38df1b0 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -31,7 +31,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager.TaskDescription;
-import android.content.res.Configuration;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -63,6 +62,16 @@
 
     private final ActivityRecord mActivityRecord;
 
+    // TODO(b/356385137): Remove these we added to make dependencies temporarily explicit.
+    @NonNull
+    private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides;
+    @NonNull
+    private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy;
+    @NonNull
+    private final TransparentPolicy mTransparentPolicy;
+    @NonNull
+    private final AppCompatOrientationOverrides mAppCompatOrientationOverrides;
+
     private boolean mShowWallpaperForLetterboxBackground;
 
     @Nullable
@@ -76,6 +85,15 @@
         // is created in its constructor. It shouldn't be used in this constructor but it's safe
         // to use it after since controller is only used in ActivityRecord.
         mActivityRecord = activityRecord;
+        // TODO(b/356385137): Remove these we added to make dependencies temporarily explicit.
+        mAppCompatReachabilityOverrides = mActivityRecord.mAppCompatController
+                .getAppCompatReachabilityOverrides();
+        mAppCompatReachabilityPolicy = mActivityRecord.mAppCompatController
+                .getAppCompatReachabilityPolicy();
+        mTransparentPolicy = mActivityRecord.mAppCompatController.getTransparentPolicy();
+        mAppCompatOrientationOverrides = mActivityRecord.mAppCompatController
+                .getAppCompatOrientationOverrides();
+
     }
 
     /** Cleans up {@link Letterbox} if it exists.*/
@@ -83,6 +101,8 @@
         if (mLetterbox != null) {
             mLetterbox.destroy();
             mLetterbox = null;
+            // TODO Remove after Letterbox refactoring.
+            mAppCompatReachabilityPolicy.setLetterboxInnerBoundsSupplier(null);
         }
     }
 
@@ -166,8 +186,7 @@
                         this::hasWallpaperBackgroundForLetterbox,
                         this::getLetterboxWallpaperBlurRadiusPx,
                         this::getLetterboxWallpaperDarkScrimAlpha,
-                        this::handleHorizontalDoubleTap,
-                        this::handleVerticalDoubleTap,
+                        mAppCompatReachabilityPolicy,
                         this::getLetterboxParentSurface);
                 mLetterbox.attachInput(w);
             }
@@ -203,12 +222,10 @@
             // For this reason we use ActivityRecord#getBounds() that the translucent activity
             // inherits from the first opaque activity beneath and also takes care of the scaling
             // in case of activities in size compat mode.
-            final Rect innerFrame = mActivityRecord.mAppCompatController
-                    .getTransparentPolicy().isRunning()
-                    ? mActivityRecord.getBounds() : w.getFrame();
+            final Rect innerFrame =
+                    mTransparentPolicy.isRunning() ? mActivityRecord.getBounds() : w.getFrame();
             mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);
-            if (mActivityRecord.mAppCompatController
-                    .getAppCompatReachabilityOverrides().isDoubleTapEvent()) {
+            if (mAppCompatReachabilityOverrides.isDoubleTapEvent()) {
                 // We need to notify Shell that letterbox position has changed.
                 mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
             }
@@ -243,36 +260,13 @@
                 && mActivityRecord.fillsParent();
     }
 
-    float getHorizontalPositionMultiplier(@NonNull Configuration parentConfiguration) {
-        return mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides()
-                .getHorizontalPositionMultiplier(parentConfiguration);
-    }
-
-    float getVerticalPositionMultiplier(@NonNull Configuration parentConfiguration) {
-        return mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides()
-                .getVerticalPositionMultiplier(parentConfiguration);
-    }
-
     boolean isLetterboxEducationEnabled() {
         return mAppCompatConfiguration.getIsEducationEnabled();
     }
 
     @VisibleForTesting
-    void handleHorizontalDoubleTap(int x) {
-        mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
-                .handleHorizontalDoubleTap(x, mLetterbox::getInnerFrame);
-    }
-
-    @VisibleForTesting
-    void handleVerticalDoubleTap(int y) {
-        mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
-                .handleVerticalDoubleTap(y, mLetterbox::getInnerFrame);
-    }
-
-    @VisibleForTesting
     boolean shouldShowLetterboxUi(WindowState mainWindow) {
-        if (mActivityRecord.mAppCompatController.getAppCompatOrientationOverrides()
-                .getIsRelaunchingAfterRequestedOrientationChanged()) {
+        if (mAppCompatOrientationOverrides.getIsRelaunchingAfterRequestedOrientationChanged()) {
             return mLastShouldShowLetterboxUi;
         }
 
@@ -361,8 +355,7 @@
         // corners because we assume the specific layout would. This is the case when the layout
         // of the translucent activity uses only a part of all the bounds because of the use of
         // LayoutParams.WRAP_CONTENT.
-        if (mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning()
-                && (cropBounds.width() != mainWindow.mRequestedWidth
+        if (mTransparentPolicy.isRunning() && (cropBounds.width() != mainWindow.mRequestedWidth
                 || cropBounds.height() != mainWindow.mRequestedHeight)) {
             return null;
         }
@@ -505,7 +498,8 @@
             return;
         }
 
-        pw.println(prefix + "  letterboxReason=" + getLetterboxReasonString(mainWin));
+        pw.println(prefix + "  letterboxReason="
+                + AppCompatUtils.getLetterboxReasonString(mActivityRecord, mainWin));
         pw.println(prefix + "  activityAspectRatio="
                 + AppCompatUtils.computeAspectRatio(mActivityRecord.getBounds()));
 
@@ -515,10 +509,10 @@
         if (!shouldShowLetterboxUi) {
             return;
         }
-        pw.println(prefix + "  isVerticalThinLetterboxed=" + mActivityRecord.mAppCompatController
-                .getAppCompatReachabilityOverrides().isVerticalThinLetterboxed());
-        pw.println(prefix + "  isHorizontalThinLetterboxed=" + mActivityRecord.mAppCompatController
-                .getAppCompatReachabilityOverrides().isHorizontalThinLetterboxed());
+        pw.println(prefix + "  isVerticalThinLetterboxed="
+                + mAppCompatReachabilityOverrides.isVerticalThinLetterboxed());
+        pw.println(prefix + "  isHorizontalThinLetterboxed="
+                + mAppCompatReachabilityOverrides.isHorizontalThinLetterboxed());
         pw.println(prefix + "  letterboxBackgroundColor=" + Integer.toHexString(
                 getLetterboxBackgroundColor().toArgb()));
         pw.println(prefix + "  letterboxBackgroundType="
@@ -542,9 +536,11 @@
         pw.println(prefix + "  isVerticalReachabilityEnabled="
                 + reachabilityOverrides.isVerticalReachabilityEnabled());
         pw.println(prefix + "  letterboxHorizontalPositionMultiplier="
-                + getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
+                + mAppCompatReachabilityOverrides.getHorizontalPositionMultiplier(mActivityRecord
+                    .getParent().getConfiguration()));
         pw.println(prefix + "  letterboxVerticalPositionMultiplier="
-                + getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
+                + mAppCompatReachabilityOverrides.getVerticalPositionMultiplier(mActivityRecord
+                    .getParent().getConfiguration()));
         pw.println(prefix + "  letterboxPositionForHorizontalReachability="
                 + AppCompatConfiguration.letterboxHorizontalReachabilityPositionToString(
                 mAppCompatConfiguration.getLetterboxPositionForHorizontalReachability(false)));
@@ -562,28 +558,6 @@
                 .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox());
     }
 
-    /**
-     * Returns a string representing the reason for letterboxing. This method assumes the activity
-     * is letterboxed.
-     */
-    private String getLetterboxReasonString(WindowState mainWin) {
-        if (mActivityRecord.inSizeCompatMode()) {
-            return "SIZE_COMPAT_MODE";
-        }
-        if (mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
-                .isLetterboxedForFixedOrientationAndAspectRatio()) {
-            return "FIXED_ORIENTATION";
-        }
-        if (mainWin.isLetterboxedForDisplayCutout()) {
-            return "DISPLAY_CUTOUT";
-        }
-        if (mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
-                .isLetterboxedForAspectRatioOnly()) {
-            return "ASPECT_RATIO";
-        }
-        return "UNKNOWN_REASON";
-    }
-
     @Nullable
     LetterboxDetails getLetterboxDetails() {
         final WindowState w = mActivityRecord.findMainWindow();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 220248c..f8cf97e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -178,6 +178,10 @@
         doReturn(enabled).when(mActivityStack.top()).shouldCreateCompatDisplayInsets();
     }
 
+    void setTopActivityInSizeCompatMode(boolean inScm) {
+        doReturn(inScm).when(mActivityStack.top()).inSizeCompatMode();
+    }
+
     void setShouldApplyUserFullscreenOverride(boolean enabled) {
         doReturn(enabled).when(mActivityStack.top().mAppCompatController
                 .getAppCompatAspectRatioOverrides()).shouldApplyUserFullscreenOverride();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
new file mode 100644
index 0000000..9e242ee
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 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.server.wm;
+
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatUtils}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatUtilsTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatUtilsTest extends WindowTestsBase {
+
+    @Test
+    public void getLetterboxReasonString_inSizeCompatMode() {
+        runTestScenario((robot) -> {
+            robot.activity().setTopActivityInSizeCompatMode(/* inScm */ true);
+
+            robot.checkTopActivityLetterboxReason(/* expected */ "SIZE_COMPAT_MODE");
+        });
+    }
+
+    @Test
+    public void getLetterboxReasonString_fixedOrientation() {
+        runTestScenario((robot) -> {
+            robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+            robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
+                    /* forFixedOrientationAndAspectRatio */ true);
+
+            robot.checkTopActivityLetterboxReason(/* expected */ "FIXED_ORIENTATION");
+        });
+    }
+
+    @Test
+    public void getLetterboxReasonString_isLetterboxedForDisplayCutout() {
+        runTestScenario((robot) -> {
+            robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+            robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
+                    /* forFixedOrientationAndAspectRatio */ false);
+            robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ true);
+
+            robot.checkTopActivityLetterboxReason(/* expected */ "DISPLAY_CUTOUT");
+        });
+    }
+
+    @Test
+    public void getLetterboxReasonString_aspectRatio() {
+        runTestScenario((robot) -> {
+            robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+            robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
+                    /* forFixedOrientationAndAspectRatio */ false);
+            robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
+            robot.setIsLetterboxedForAspectRatioOnly(/* forAspectRatio */ true);
+
+            robot.checkTopActivityLetterboxReason(/* expected */ "ASPECT_RATIO");
+        });
+    }
+
+    @Test
+    public void getLetterboxReasonString_unknownReason() {
+        runTestScenario((robot) -> {
+            robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+            robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
+                    /* forFixedOrientationAndAspectRatio */ false);
+            robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
+            robot.setIsLetterboxedForAspectRatioOnly(/* forAspectRatio */ false);
+
+            robot.checkTopActivityLetterboxReason(/* expected */ "UNKNOWN_REASON");
+        });
+    }
+
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    void runTestScenario(@NonNull Consumer<AppCompatUtilsRobotTest> consumer) {
+        final AppCompatUtilsRobotTest robot = new AppCompatUtilsRobotTest(mWm, mAtm, mSupervisor);
+        consumer.accept(robot);
+    }
+
+    private static class AppCompatUtilsRobotTest extends AppCompatRobotBase {
+
+        private final WindowState mWindowState;
+
+        AppCompatUtilsRobotTest(@NonNull WindowManagerService wm,
+                @NonNull ActivityTaskManagerService atm,
+                @NonNull ActivityTaskSupervisor supervisor) {
+            super(wm, atm, supervisor);
+            activity().createActivityWithComponent();
+            mWindowState = Mockito.mock(WindowState.class);
+        }
+
+        void setIsLetterboxedForFixedOrientationAndAspectRatio(
+                boolean forFixedOrientationAndAspectRatio) {
+            when(activity().top().mAppCompatController.getAppCompatAspectRatioPolicy()
+                    .isLetterboxedForFixedOrientationAndAspectRatio())
+                        .thenReturn(forFixedOrientationAndAspectRatio);
+        }
+
+        void setIsLetterboxedForAspectRatioOnly(boolean forAspectRatio) {
+            when(activity().top().mAppCompatController.getAppCompatAspectRatioPolicy()
+                    .isLetterboxedForAspectRatioOnly()).thenReturn(forAspectRatio);
+        }
+
+        void setIsLetterboxedForDisplayCutout(boolean displayCutout) {
+            when(mWindowState.isLetterboxedForDisplayCutout()).thenReturn(displayCutout);
+        }
+
+        void checkTopActivityLetterboxReason(@NonNull String expected) {
+            Assert.assertEquals(expected,
+                    AppCompatUtils.getLetterboxReasonString(activity().top(), mWindowState));
+        }
+
+    }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index fbc4c7b..ffaa2d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -66,7 +66,7 @@
         mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
                 () -> mAreCornersRounded, () -> Color.valueOf(mColor),
                 () -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha,
-                /* doubleTapCallbackX= */ x -> {}, /* doubleTapCallbackY= */ y -> {},
+                mock(AppCompatReachabilityPolicy.class),
                 () -> mParentSurface);
         mTransaction = spy(StubTransaction.class);
     }
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 7dc3b07..3e68b6b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -281,7 +281,8 @@
         if (horizontalReachability) {
             final Consumer<Integer> doubleClick =
                     (Integer x) -> {
-                        mActivity.mLetterboxUiController.handleHorizontalDoubleTap(x);
+                        mActivity.mAppCompatController.getAppCompatReachabilityPolicy()
+                                .handleDoubleTap(x, displayHeight / 2);
                         mActivity.mRootWindowContainer.performSurfacePlacement();
                     };
 
@@ -310,7 +311,8 @@
         } else {
             final Consumer<Integer> doubleClick =
                     (Integer y) -> {
-                        mActivity.mLetterboxUiController.handleVerticalDoubleTap(y);
+                        mActivity.mAppCompatController.getAppCompatReachabilityPolicy()
+                                .handleDoubleTap(displayWidth / 2, y);
                         mActivity.mRootWindowContainer.performSurfacePlacement();
                     };
 
@@ -373,7 +375,8 @@
 
         final Consumer<Integer> doubleClick =
                 (Integer y) -> {
-                    activity.mLetterboxUiController.handleVerticalDoubleTap(y);
+                    activity.mAppCompatController.getAppCompatReachabilityPolicy()
+                            .handleDoubleTap(dw / 2, y);
                     activity.mRootWindowContainer.performSurfacePlacement();
                 };
 
@@ -4317,15 +4320,17 @@
         resizeDisplay(mTask.mDisplayContent, 1400, 2800);
 
         // Make sure app doesn't jump to top (default tabletop position) when unfolding.
-        assertEquals(1.0f, mActivity.mLetterboxUiController.getVerticalPositionMultiplier(
-                mActivity.getParent().getConfiguration()), 0);
+        assertEquals(1.0f, mActivity.mAppCompatController
+                .getAppCompatReachabilityOverrides().getVerticalPositionMultiplier(mActivity
+                        .getParent().getConfiguration()), 0);
 
         // Simulate display fully open after unfolding.
         setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
         doReturn(false).when(mActivity.mDisplayContent).inTransition();
 
-        assertEquals(1.0f, mActivity.mLetterboxUiController.getVerticalPositionMultiplier(
-                mActivity.getParent().getConfiguration()), 0);
+        assertEquals(1.0f, mActivity.mAppCompatController
+                .getAppCompatReachabilityOverrides().getVerticalPositionMultiplier(mActivity
+                        .getParent().getConfiguration()), 0);
     }
 
     @Test