Launch SizeCompat Activities in a Window on Freeform Desktop Mode

Currently, when a display is in freeform mode, an activity that
is not resizable launches in fullscreen. However, for large
screen devices, it is preferable to launch activities in windows
to create the true desktop experience. This CL changes launch
configurations and resize actions to make non-resizable activities
launch in a window and to prevent resizing of these windows. When
the display enters fullscreen mode, the activity keeps its original
bounds and launches in a letterbox.

To make this all work, ActivityRecord was updated to use the
hierarchy instead of trying to recalculate everything independently
with a fullscreen assumption. The result is that the ActivityRecord
configuration now (mostly) has the actual bounds of the app so
that positioning works properly.

Please note, the following tests were removed because the
functionality they were trying to test has changed. Previously,
when a task was nonresizeable, even if the display windowing mode
was freeform, it was launched in fullscreen mode. However this CL
allows nonresizeable tasks in freeform mode to be launched in
windows. These tests were removed since they are redundant
with non-resizable (which is handled by packagemanager):
- TaskLaunchParamsModifierTests#testForceMaximizesPreDApp
- TaskLaunchParamsModifierTests#testForceMaximizesAppWithoutMultipleDensitySupport

The following tests were added:
- TaskPositioningControllerTests#testHandleTapOutsideNonResizableTask
- TaskLaunchParamsModifierTests#testLaunchesAppInWindowOnFreeformDisplay
- ActivityRecordTests#testSizeCompatMode_KeepBoundsWhenChangingFromFreeformToFullscreen

This also adds a developer option to enable size-compat
apps to start in freeform mode.

Test: go/wm-smoke
Change-Id: I3aa3fcdcd2b1e0b875d61dfaed3d5e85313edc29
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bdc7834..49710e5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8803,6 +8803,13 @@
         public static final String DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS =
                 "force_desktop_mode_on_external_displays";
 
+        /**
+         * Whether to allow non-resizable apps to be freeform.
+         * @hide
+         */
+        public static final String DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM =
+                "enable_sizecompat_freeform";
+
        /**
         * Whether user has enabled development settings.
         */
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index a568c13..8d1fbe5 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -281,6 +281,7 @@
         optional SettingProto force_rtl = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto emulate_display_cutout = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto force_desktop_mode_on_external_displays = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto enable_sizecompat_freeform = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Development development = 39;
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 6bd26bf..dbb25ab 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -548,6 +548,9 @@
         dumpSetting(s, p,
                 Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
                 GlobalSettingsProto.Development.FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS);
+        dumpSetting(s, p,
+                Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM,
+                GlobalSettingsProto.Development.ENABLE_SIZECOMPAT_FREEFORM);
         p.end(developmentToken);
 
         final long deviceToken = p.start(GlobalSettingsProto.DEVICE);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 62827bc..7ea555d 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -222,6 +222,7 @@
                     Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
                     Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
                     Settings.Global.DEVELOPMENT_FORCE_RTL,
+                    Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM,
                     Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
                     Settings.Global.DEVICE_DEMO_MODE,
                     Settings.Global.DEVICE_IDLE_CONSTANTS,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3853841..273a283 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -299,10 +299,10 @@
 import android.view.WindowManager.LayoutParams;
 import android.view.animation.Animation;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.ReferrerIntent;
-import com.android.internal.R;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.XmlUtils;
 import com.android.server.AttributeCache;
@@ -6599,14 +6599,25 @@
 
             // The role of CompatDisplayInsets is like the override bounds.
             final ActivityDisplay display = getDisplay();
-            if (display != null && display.mDisplayContent != null) {
-                mCompatDisplayInsets = new CompatDisplayInsets(display.mDisplayContent);
+            if (display != null) {
+                if (display.inFreeformWindowingMode()) {
+                    // in freeform, apps in compat mode still launch in windows so don't want to
+                    // use display bounds, but rather use TaskRecord bounds.
+                    if (getTaskRecord() != null) {
+                        mCompatDisplayInsets = new CompatDisplayInsets(getTaskRecord());
+                    }
+                } else if (display.mDisplayContent != null) {
+                    mCompatDisplayInsets = new CompatDisplayInsets(display.mDisplayContent);
+                }
             }
         } else {
             // We must base this on the parent configuration, because we set our override
             // configuration's appBounds based on the result of this method. If we used our own
             // configuration, it would be influenced by past invocations.
-            computeBounds(mTmpBounds, getParent().getWindowConfiguration().getAppBounds());
+
+            // inherits from parent by default
+            mTmpBounds.set(getRequestedOverrideBounds());
+            applyAspectRatio(mTmpBounds, getParent().getWindowConfiguration().getAppBounds());
 
             if (mTmpBounds.equals(getRequestedOverrideBounds())) {
                 // The bounds is not changed or the activity is resizable (both the 2 bounds are
@@ -6673,12 +6684,6 @@
             // fixed orientation.
             orientation = parentOrientation;
         } else {
-            if (!resolvedBounds.isEmpty()
-                    // The decor insets may be different according to the rotation.
-                    && getWindowConfiguration().getRotation() == parentRotation) {
-                // Keep the computed resolved override configuration.
-                return;
-            }
             final int requestedOrientation = getRequestedConfigurationOrientation();
             if (requestedOrientation != ORIENTATION_UNDEFINED) {
                 orientation = requestedOrientation;
@@ -6687,57 +6692,53 @@
 
         super.resolveOverrideConfiguration(newParentConfiguration);
 
-        boolean useParentOverrideBounds = false;
-        final Rect displayBounds = mTmpBounds;
-        final Rect containingAppBounds = new Rect();
-        if (task.handlesOrientationChangeFromDescendant()) {
-            // Prefer to use the orientation which is determined by this activity to calculate
-            // bounds because the parent will follow the requested orientation.
-            mCompatDisplayInsets.getDisplayBoundsByOrientation(displayBounds, orientation);
-        } else {
-            // The parent hierarchy doesn't handle the orientation changes. This is usually because
-            // the aspect ratio of display is close to square or the display rotation is fixed.
-            // In this case, task will compute override bounds to fit the app with respect to the
-            // requested orientation. So here we perform similar calculation to have consistent
-            // bounds even the original parent hierarchies were changed.
-            final int baseOrientation = task.getParent().getConfiguration().orientation;
-            mCompatDisplayInsets.getDisplayBoundsByOrientation(displayBounds, baseOrientation);
-            task.computeFullscreenBounds(containingAppBounds, this, displayBounds, baseOrientation);
-            useParentOverrideBounds = !containingAppBounds.isEmpty();
-        }
+        Rect containingAppBounds = new Rect();
+        Rect parentBounds = new Rect(newParentConfiguration.windowConfiguration.getBounds());
 
-        // The offsets will be non-zero if the parent has override bounds.
-        final int containingOffsetX = containingAppBounds.left;
-        final int containingOffsetY = containingAppBounds.top;
-        if (!useParentOverrideBounds) {
-            containingAppBounds.set(displayBounds);
-        }
+        // Use compat insets to lock width and height. We should not use the parent width and height
+        // because apps in compat mode should have a constant width and height. The compat insets
+        // are locked when the app is first launched and are never changed after that, so we can
+        // rely on them to contain the original and unchanging width and height of the app.
+        final Rect compatDisplayBounds = mTmpBounds;
+        mCompatDisplayInsets.getDisplayBoundsByOrientation(compatDisplayBounds, orientation);
+        containingAppBounds.set(0, 0, compatDisplayBounds.width(), compatDisplayBounds.height());
+
+        resolvedBounds.set(containingAppBounds);
+
         if (parentRotation != ROTATION_UNDEFINED) {
-            // Ensure the container bounds won't overlap with the decors.
-            TaskRecord.intersectWithInsetsIfFits(containingAppBounds, displayBounds,
+            // Ensure the parent and container bounds won't overlap with insets.
+            TaskRecord.intersectWithInsetsIfFits(containingAppBounds, compatDisplayBounds,
+                    mCompatDisplayInsets.mNonDecorInsets[parentRotation]);
+            TaskRecord.intersectWithInsetsIfFits(parentBounds, compatDisplayBounds,
                     mCompatDisplayInsets.mNonDecorInsets[parentRotation]);
         }
 
-        computeBounds(resolvedBounds, containingAppBounds);
-        if (resolvedBounds.isEmpty()) {
-            // Use the entire available bounds because there is no restriction.
-            resolvedBounds.set(useParentOverrideBounds ? containingAppBounds : displayBounds);
-        } else {
-            // The offsets are included in width and height by {@link #computeBounds}, so we have to
-            // restore it.
-            resolvedBounds.left += containingOffsetX;
-            resolvedBounds.top += containingOffsetY;
-        }
+        applyAspectRatio(resolvedBounds, containingAppBounds);
+
+        // Center horizontally in parent and align to top of parent - this is a UX choice
+        final int left = parentBounds.left + parentBounds.width() / 2 - resolvedBounds.width() / 2;
+        resolvedBounds.set(left, parentBounds.top, left + resolvedBounds.width(),
+                parentBounds.top + resolvedBounds.height());
+
+        // We want to get as much of the app on the screen even if insets cover it. This is because
+        // insets change but an app's bounds are more permanent after launch. After computing insets
+        // and horizontally centering resolvedBounds, the resolvedBounds may end up outside parent
+        // bounds. This is okay only if the resolvedBounds exceed their parent on the bottom and
+        // right, because that is clipped when the final bounds are computed. To reach this state,
+        // we first try and push the app as much inside the parent towards the top and left (the
+        // min). The app may then end up outside the parent by going too far left and top, so we
+        // push it back into the parent by taking the max with parent left and top.
+        Rect fullParentBounds = newParentConfiguration.windowConfiguration.getBounds();
+        resolvedBounds.offsetTo(Math.max(fullParentBounds.left,
+                Math.min(fullParentBounds.right - resolvedBounds.width(), resolvedBounds.left)),
+                Math.max(fullParentBounds.top,
+                        Math.min(fullParentBounds.bottom - resolvedBounds.height(),
+                                resolvedBounds.top)));
+
+        // Use resolvedBounds to compute other override configurations such as appBounds
         task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
                 mCompatDisplayInsets);
 
-        // The horizontal inset included in width is not needed if the activity cannot fill the
-        // parent, because the offset will be applied by {@link ActivityRecord#mSizeCompatBounds}.
-        final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
-        final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
-        if (resolvedBounds.width() < parentAppBounds.width()) {
-            resolvedBounds.right -= resolvedAppBounds.left;
-        }
         // Use parent orientation if it cannot be decided by bounds, so the activity can fit inside
         // the parent bounds appropriately.
         if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) {
@@ -6918,11 +6919,11 @@
     }
 
     /**
-     * Computes the bounds to fit the Activity within the bounds of the {@link Configuration}.
+     * Applies aspect ratio restrictions to outBounds. If no restrictions, then no change is
+     * made to outBounds.
      */
     // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
-    private void computeBounds(Rect outBounds, Rect containingAppBounds) {
-        outBounds.setEmpty();
+    private void applyAspectRatio(Rect outBounds, Rect containingAppBounds) {
         final float maxAspectRatio = info.maxAspectRatio;
         final ActivityStack stack = getActivityStack();
         final float minAspectRatio = info.minAspectRatio;
@@ -6991,12 +6992,6 @@
 
         if (containingAppWidth <= activityWidth && containingAppHeight <= activityHeight) {
             // The display matches or is less than the activity aspect ratio, so nothing else to do.
-            // Return the existing bounds. If this method is running for the first time,
-            // {@link #getRequestedOverrideBounds()} will be empty (representing no override). If
-            // the method has run before, then effect of {@link #getRequestedOverrideBounds()} will
-            // already have been applied to the value returned from {@link getConfiguration}. Refer
-            // to {@link TaskRecord#computeConfigResourceOverrides()}.
-            outBounds.set(getRequestedOverrideBounds());
             return;
         }
 
@@ -7776,6 +7771,20 @@
             }
         }
 
+        /**
+         * Sets bounds to {@link TaskRecord} bounds. For apps in freeform, the task bounds are the
+         * parent bounds from the app's perspective. No insets because within a window.
+         */
+        CompatDisplayInsets(TaskRecord taskRecord) {
+            Rect taskBounds = taskRecord.getConfiguration().windowConfiguration.getBounds();
+            mDisplayWidth = taskBounds.width();
+            mDisplayHeight = taskBounds.height();
+            for (int rotation = 0; rotation < 4; rotation++) {
+                mNonDecorInsets[rotation] = new Rect();
+                mStableInsets[rotation] = new Rect();
+            }
+        }
+
         void getDisplayBoundsByRotation(Rect outBounds, int rotation) {
             final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
             final int dw = rotated ? mDisplayHeight : mDisplayWidth;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 14df505..014b682 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -54,6 +54,7 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
 import static android.provider.Settings.Global.HIDE_ERROR_DIALOGS;
@@ -569,6 +570,7 @@
     boolean mSupportsPictureInPicture;
     boolean mSupportsMultiDisplay;
     boolean mForceResizableActivities;
+    boolean mSizeCompatFreeform;
 
     final List<ActivityTaskManagerInternal.ScreenObserver> mScreenObservers = new ArrayList<>();
 
@@ -744,6 +746,8 @@
         final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
         final boolean forceResizable = Settings.Global.getInt(
                 resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
+        final boolean sizeCompatFreeform = Settings.Global.getInt(
+                resolver, DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM, 0) != 0;
 
         // Transfer any global setting for forcing RTL layout, into a System Property
         DisplayProperties.debug_force_rtl(forceRtl);
@@ -757,6 +761,7 @@
 
         synchronized (mGlobalLock) {
             mForceResizableActivities = forceResizable;
+            mSizeCompatFreeform = sizeCompatFreeform;
             final boolean multiWindowFormEnabled = freeformWindowManagement
                     || supportsSplitScreenMultiWindow
                     || supportsPictureInPicture
@@ -3393,6 +3398,9 @@
 
                 if (stack.inFreeformWindowingMode()) {
                     stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+                } else if (!mSizeCompatFreeform) {
+                    throw new IllegalStateException("Size-compat windows are currently not"
+                            + "freeform-enabled");
                 } else if (stack.getParent().inFreeformWindowingMode()) {
                     // If the window is on a freeform display, set it to undefined. It will be
                     // resolved to freeform and it can adjust windowing mode when the display mode
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 9712277..31145de 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -43,10 +43,8 @@
 import android.app.ActivityOptions;
 import android.app.WindowConfiguration;
 import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
-import android.os.Build;
 import android.util.Slog;
 import android.view.Gravity;
 import android.view.View;
@@ -65,15 +63,6 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskLaunchParamsModifier" : TAG_ATM;
     private static final boolean DEBUG = false;
 
-    // A mask for SUPPORTS_SCREEN that indicates the activity supports resize.
-    private static final int SUPPORTS_SCREEN_RESIZEABLE_MASK =
-            ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES
-                    | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS
-                    | ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS
-                    | ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS
-                    | ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES
-                    | ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS;
-
     // Screen size of Nexus 5x
     private static final int DEFAULT_PORTRAIT_PHONE_WIDTH_DP = 412;
     private static final int DEFAULT_PORTRAIT_PHONE_HEIGHT_DP = 732;
@@ -253,10 +242,9 @@
         if (display.inFreeformWindowingMode()) {
             if (launchMode == WINDOWING_MODE_PINNED) {
                 if (DEBUG) appendLog("picture-in-picture");
-            } else if (isTaskForcedMaximized(root)) {
-                // We're launching an activity that probably can't handle resizing nicely, so force
-                // it to be maximized even someone suggests launching it in freeform using launch
-                // options.
+            } else if (!mSupervisor.mService.mSizeCompatFreeform && !root.isResizeable()) {
+                // We're launching an activity in size-compat mode and they aren't allowed in
+                // freeform, so force it to be maximized.
                 launchMode = WINDOWING_MODE_FULLSCREEN;
                 outParams.mBounds.setEmpty();
                 if (DEBUG) appendLog("forced-maximize");
@@ -460,28 +448,6 @@
     }
 
     /**
-     * Returns if task is forced to maximize.
-     *
-     * There are several cases where we force a task to maximize:
-     * 1) Root activity is targeting pre-Donut, which by default can't handle multiple screen
-     *    densities, so resizing will likely cause issues;
-     * 2) Root activity doesn't declare any flag that it supports any screen density, so resizing
-     *    may also cause issues;
-     * 3) Root activity is not resizeable, for which we shouldn't allow user resize it.
-     *
-     * @param root the root activity to check against.
-     * @return {@code true} if it should be forced to maximize; {@code false} otherwise.
-     */
-    private boolean isTaskForcedMaximized(@NonNull ActivityRecord root) {
-        if (root.info.applicationInfo.targetSdkVersion < Build.VERSION_CODES.DONUT
-                || (root.info.applicationInfo.flags & SUPPORTS_SCREEN_RESIZEABLE_MASK) == 0) {
-            return true;
-        }
-
-        return !root.isResizeable();
-    }
-
-    /**
      * Resolves activity requested orientation to 4 categories:
      * 1) {@link ActivityInfo#SCREEN_ORIENTATION_LOCKED} indicating app wants to lock down
      *    orientation;
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index ed07f30..e1123fa 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -126,6 +126,11 @@
             synchronized (mService.mGlobalLock) {
                 final Task task = displayContent.findTaskForResizePoint(x, y);
                 if (task != null) {
+                    if (!task.isResizeable()) {
+                        // The task is not resizable, so don't do anything when the user drags the
+                        // the resize handles.
+                        return;
+                    }
                     if (!startPositioningLocked(task.getTopVisibleAppMainWindow(), true /*resize*/,
                             task.preserveOrientationOnResize(), x, y)) {
                         return;
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 299b32c..09c25f0 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -519,18 +519,7 @@
         mAtmService.deferWindowLayout();
 
         try {
-            if (!isResizeable()) {
-                Slog.w(TAG, "resizeTask: task " + this + " not resizeable.");
-                return true;
-            }
-
-            // If this is a forced resize, let it go through even if the bounds is not changing,
-            // as we might need a relayout due to surface size change (to/from fullscreen).
             final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0;
-            if (equivalentRequestedOverrideBounds(bounds) && !forced) {
-                // Nothing to do here...
-                return true;
-            }
 
             if (mTask == null) {
                 // Task doesn't exist in window manager yet (e.g. was restored from recents).
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e90f3da..415606b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -39,6 +39,7 @@
 import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP;
 import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS;
 import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -717,6 +718,8 @@
                 Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT);
         private final Uri mForceResizableUri = Settings.Global.getUriFor(
                 DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
+        private final Uri mSizeCompatFreeformUri = Settings.Global.getUriFor(
+                DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM);
 
         public SettingsObserver() {
             super(new Handler());
@@ -737,6 +740,8 @@
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(mFreeformWindowUri, false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(mForceResizableUri, false, this, UserHandle.USER_ALL);
+            resolver.registerContentObserver(mSizeCompatFreeformUri, false, this,
+                    UserHandle.USER_ALL);
         }
 
         @Override
@@ -775,6 +780,11 @@
                 return;
             }
 
+            if (mSizeCompatFreeformUri.equals(uri)) {
+                updateSizeCompatFreeform();
+                return;
+            }
+
             @UpdateAnimationScaleMode
             final int mode;
             if (mWindowAnimationScaleUri.equals(uri)) {
@@ -844,6 +854,14 @@
 
             mAtmService.mForceResizableActivities = forceResizable;
         }
+
+        void updateSizeCompatFreeform() {
+            ContentResolver resolver = mContext.getContentResolver();
+            final boolean sizeCompatFreeform = Settings.Global.getInt(resolver,
+                    DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM, 0) != 0;
+
+            mAtmService.mSizeCompatFreeform = sizeCompatFreeform;
+        }
     }
 
     PowerManager mPowerManager;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b9cf29a..991241d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2319,29 +2319,6 @@
         final Region region = inputWindowHandle.touchableRegion;
         setTouchableRegionCropIfNeeded(inputWindowHandle);
 
-        final Rect appOverrideBounds = mActivityRecord != null
-                ? mActivityRecord.getResolvedOverrideBounds() : null;
-        if (appOverrideBounds != null && !appOverrideBounds.isEmpty()) {
-            // There may have touchable letterboxes around the activity, so in order to let the
-            // letterboxes are able to receive touch event and slip to activity, the activity with
-            // compatibility bounds cannot occupy full screen touchable region.
-            if (modal) {
-                // A modal window uses the whole compatibility bounds.
-                flags |= FLAG_NOT_TOUCH_MODAL;
-                mTmpRect.set(0, 0, appOverrideBounds.width(), appOverrideBounds.height());
-            } else {
-                // Non-modal uses the application based frame.
-                mTmpRect.set(mWindowFrames.mCompatFrame);
-            }
-            // The offset of compatibility bounds is applied to surface of {@link #ActivityRecord}
-            // and frame, so it is unnecessary to translate twice in surface based coordinates.
-            final int surfaceOffsetX = mActivityRecord.hasSizeCompatBounds()
-                    ? mActivityRecord.getBounds().left : 0;
-            mTmpRect.offset(surfaceOffsetX - mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top);
-            region.set(mTmpRect);
-            return flags;
-        }
-
         if (modal && mActivityRecord != null) {
             // Limit the outer touch to the activity stack region.
             flags |= FLAG_NOT_TOUCH_MODAL;
@@ -2398,6 +2375,15 @@
         // Translate to surface based coordinates.
         region.translate(-mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top);
 
+        // TODO(b/139804591): sizecompat layout needs to be reworked. Currently mFrame is post-
+        // scaling but the existing logic doesn't expect that. The result is that the already-
+        // scaled region ends up getting sent to surfaceflinger which then applies the scale
+        // (again). Until this is resolved, apply an inverse-scale here.
+        if (mActivityRecord != null && mActivityRecord.hasSizeCompatBounds()
+                && mGlobalScale != 1.f) {
+            region.scale(mInvGlobalScale);
+        }
+
         return flags;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index bf1508a..e43a364 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -40,7 +40,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED;
 import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED;
 import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED;
@@ -70,10 +69,12 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions;
+import android.app.WindowConfiguration;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.PauseActivityItem;
@@ -485,6 +486,42 @@
     }
 
     @Test
+    public void testSizeCompatMode_KeepBoundsWhenChangingFromFreeformToFullscreen() {
+        setupDisplayContentForCompatDisplayInsets();
+
+        // put display in freeform mode
+        ActivityDisplay display = mActivity.getDisplay();
+        final Configuration c = new Configuration(display.getRequestedOverrideConfiguration());
+        c.windowConfiguration.setBounds(new Rect(0, 0, 2000, 1000));
+        c.densityDpi = 300;
+        c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+        display.onRequestedOverrideConfigurationChanged(c);
+
+        // launch compat activity in freeform and store bounds
+        mActivity.mAppWindowToken.mOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+        mTask.getRequestedOverrideConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
+        mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+        mActivity.visible = true;
+        ensureActivityConfiguration();
+
+        final Rect bounds = new Rect(mActivity.getBounds());
+        final int density = mActivity.getConfiguration().densityDpi;
+        final int windowingMode = mActivity.getWindowingMode();
+
+        // change display configuration to fullscreen
+        c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+        display.onRequestedOverrideConfigurationChanged(c);
+
+        // check if dimensions stay the same
+        assertTrue(mActivity.inSizeCompatMode());
+        assertEquals(bounds.width(), mActivity.getBounds().width());
+        assertEquals(bounds.height(), mActivity.getBounds().height());
+        assertEquals(density, mActivity.getConfiguration().densityDpi);
+        assertEquals(windowingMode, mActivity.getWindowingMode());
+    }
+
+    @Test
     public void testSizeCompatMode_FixedAspectRatioBoundsWithDecor() {
         setupDisplayContentForCompatDisplayInsets();
         final int decorHeight = 200; // e.g. The device has cutout.
@@ -506,6 +543,7 @@
                 .when(mActivity).getRequestedOrientation();
         mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
         mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1;
+        mActivity.visible = true;
         ensureActivityConfiguration();
         // The parent configuration doesn't change since the first resolved configuration, so the
         // activity shouldn't be in the size compatibility mode.
@@ -555,7 +593,10 @@
         // Move the non-resizable activity to the new display.
         mStack.reparent(newDisplay, true /* onTop */, false /* displayRemoved */);
 
-        assertEquals(originalBounds, mActivity.getWindowConfiguration().getBounds());
+        assertEquals(originalBounds.width(),
+                mActivity.getWindowConfiguration().getBounds().width());
+        assertEquals(originalBounds.height(),
+                mActivity.getWindowConfiguration().getBounds().height());
         assertEquals(originalDpi, mActivity.getConfiguration().densityDpi);
         assertTrue(mActivity.inSizeCompatMode());
     }
@@ -566,9 +607,12 @@
         when(mActivity.getRequestedOrientation()).thenReturn(
                 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
         mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds());
-        mTask.getConfiguration().orientation = ORIENTATION_PORTRAIT;
+        mTask.getRequestedOverrideConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
+        mActivity.mAppWindowToken.mOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
         mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
-        mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+        mActivity.visible = true;
+
         ensureActivityConfiguration();
         final Rect originalBounds = new Rect(mActivity.getBounds());
 
@@ -576,7 +620,10 @@
         setupDisplayAndParentSize(1000, 2000);
         ensureActivityConfiguration();
 
-        assertEquals(originalBounds, mActivity.getWindowConfiguration().getBounds());
+        assertEquals(originalBounds.width(),
+                mActivity.getWindowConfiguration().getBounds().width());
+        assertEquals(originalBounds.height(),
+                mActivity.getWindowConfiguration().getBounds().height());
         assertTrue(mActivity.inSizeCompatMode());
     }
 
@@ -1242,6 +1289,8 @@
         c.windowConfiguration.setBounds(new Rect(0, 0, width, height));
         c.windowConfiguration.setAppBounds(0, 0, width, height);
         c.windowConfiguration.setRotation(ROTATION_0);
+        c.orientation = width > height
+                ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
         mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
         return displayContent;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index dd85f69..5a141ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -410,51 +410,8 @@
     }
 
     @Test
-    public void testForceMaximizesPreDApp() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
-                WINDOWING_MODE_FREEFORM);
-
-        final ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
-        options.setLaunchBounds(new Rect(0, 0, 200, 100));
-
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
-        mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
-        mCurrent.mBounds.set(0, 0, 200, 100);
-
-        mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUPCAKE;
-
-        assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
-                mActivity, /* source */ null, options, mCurrent, mResult));
-
-        assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
-                WINDOWING_MODE_FREEFORM);
-    }
-
-    @Test
-    public void testForceMaximizesAppWithoutMultipleDensitySupport() {
-        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
-                WINDOWING_MODE_FREEFORM);
-
-        final ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
-        options.setLaunchBounds(new Rect(0, 0, 200, 100));
-
-        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
-        mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
-        mCurrent.mBounds.set(0, 0, 200, 100);
-
-        mActivity.info.applicationInfo.flags = 0;
-
-        assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
-                mActivity, /* source */ null, options, mCurrent, mResult));
-
-        assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
-                WINDOWING_MODE_FREEFORM);
-    }
-
-    @Test
     public void testForceMaximizesUnresizeableApp() {
+        mService.mSizeCompatFreeform = false;
         final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
                 WINDOWING_MODE_FREEFORM);
 
@@ -476,6 +433,33 @@
     }
 
     @Test
+    public void testLaunchesAppInWindowOnFreeformDisplay() {
+        mService.mSizeCompatFreeform = true;
+        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+                WINDOWING_MODE_FREEFORM);
+
+        Rect expectedLaunchBounds = new Rect(0, 0, 200, 100);
+
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+        options.setLaunchBounds(expectedLaunchBounds);
+
+        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
+        mCurrent.mBounds.set(expectedLaunchBounds);
+
+        mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+
+        assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+                mActivity, /* source */ null, options, mCurrent, mResult));
+
+        assertEquals(expectedLaunchBounds, mResult.mBounds);
+
+        assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+                WINDOWING_MODE_FREEFORM);
+    }
+
+    @Test
     public void testSkipsForceMaximizingAppsOnNonFreeformDisplay() {
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index dc89f50..f8d49ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -64,6 +66,7 @@
                 any(InputChannel.class))).thenReturn(true);
 
         mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
+        mWindow.getTask().setResizeable(RESIZE_MODE_RESIZEABLE);
         mWindow.mInputChannel = new InputChannel();
         mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
         doReturn(mock(InputMonitor.class)).when(mDisplayContent).getInputMonitor();
@@ -129,4 +132,23 @@
         assertFalse(mTarget.isPositioningLocked());
         assertNull(mTarget.getDragWindowHandleLocked());
     }
+
+    @Test
+    public void testHandleTapOutsideNonResizableTask() {
+        assertFalse(mTarget.isPositioningLocked());
+        assertNull(mTarget.getDragWindowHandleLocked());
+
+        final DisplayContent content = mock(DisplayContent.class);
+        doReturn(mWindow.getTask()).when(content).findTaskForResizePoint(anyInt(), anyInt());
+        assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow());
+
+        mWindow.getTask().setResizeable(RESIZE_MODE_UNRESIZEABLE);
+
+        mTarget.handleTapOutsideTask(content, 0, 0);
+        // Wait until the looper processes finishTaskPositioning.
+        assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS));
+
+        assertFalse(mTarget.isPositioningLocked());
+    }
+
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java
index 86f0f8b..1c9eed2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
 
@@ -77,6 +78,20 @@
         }
     }
 
+    @Test
+    public void testEnableSizeCompatFreeform() {
+        try (SettingsSession enableSizeCompatFreeformSession = new
+                SettingsSession(DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM)) {
+            final boolean enableSizeCompatFreeform =
+                    !enableSizeCompatFreeformSession.getSetting();
+            final Uri enableSizeCompatFreeformUri =
+                    enableSizeCompatFreeformSession.setSetting(enableSizeCompatFreeform);
+            mWm.mSettingsObserver.onChange(false, enableSizeCompatFreeformUri);
+
+            assertEquals(mWm.mAtmService.mSizeCompatFreeform, enableSizeCompatFreeform);
+        }
+    }
+
     private class SettingsSession implements AutoCloseable {
 
         private static final int SETTING_VALUE_OFF = 0;