Add dev option to force desktop mode

When turned on, this will:
- Enable system decorations on external screens
- Enable freeform windowing mode on external displays
- TODO: Show mouse pointer on external display

Bug: 112451761
Test: DisplaySettingsTests
Test: DesktopModePreferenceControllerTest
Test: FreeformWindowsPreferenceControllerTest
Change-Id: If46399b9d6b8faa2a11fbdf8a4bbfef1284147c8
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a30e38a..fb1edfb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9162,6 +9162,13 @@
         public static final String DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT
                 = "enable_freeform_support";
 
+        /**
+         * Whether to enable experimental desktop mode on secondary displays.
+         * @hide
+         */
+        public static final String DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS =
+                "force_desktop_mode_on_external_displays";
+
        /**
         * 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 69ebb59..e0e7b6f 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -270,6 +270,7 @@
         optional SettingProto enable_freeform_windows_support = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
         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 Development development = 39;
 
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index d5dc903..9db7cf5 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -197,6 +197,7 @@
                     Settings.Global.DESK_DOCK_SOUND,
                     Settings.Global.DESK_UNDOCK_SOUND,
                     Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT,
+                    Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
                     Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
                     Settings.Global.DEVELOPMENT_FORCE_RTL,
                     Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index b46c288..d4ae8e5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -481,6 +481,9 @@
         dumpSetting(s, p,
                 Settings.Global.EMULATE_DISPLAY_CUTOUT,
                 GlobalSettingsProto.Development.EMULATE_DISPLAY_CUTOUT);
+        dumpSetting(s, p,
+                Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
+                GlobalSettingsProto.Development.FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS);
         p.end(developmentToken);
 
         final long deviceToken = p.start(GlobalSettingsProto.DEVICE);
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 5fb1def..d650871 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -1061,13 +1061,12 @@
     }
 
     /**
+     * Checks if system decorations should be shown on this display.
+     *
      * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
      */
     boolean supportsSystemDecorations() {
-        return mDisplay.supportsSystemDecorations()
-                // TODO (b/111363427): Remove this and set the new FLAG_SHOULD_SHOW_LAUNCHER flag
-                // (b/114338689) whenever vr 2d display id is set.
-                || mDisplayId == mSupervisor.mService.mVr2dDisplayId;
+        return mWindowContainerController.supportsSystemDecorations();
     }
 
     private boolean shouldDestroyContentOnRemove() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3359eac8..dc07a7c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -56,7 +56,6 @@
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
-import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
 import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
@@ -652,8 +651,6 @@
                 ActivityTaskManager.supportsSplitScreenMultiWindow(mContext);
         final boolean supportsMultiDisplay = mContext.getPackageManager()
                 .hasSystemFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
-        final boolean alwaysFinishActivities =
-                Settings.Global.getInt(resolver, ALWAYS_FINISH_ACTIVITIES, 0) != 0;
         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;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 348b2af..570bd23 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4651,4 +4651,16 @@
 
         pendingLayoutChanges |= changes;
     }
+
+    /**
+     * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
+     */
+    boolean supportsSystemDecorations() {
+        // TODO(b/114338689): Read the setting from DisplaySettings.
+        return mDisplay.supportsSystemDecorations()
+                // TODO (b/111363427): Remove this and set the new FLAG_SHOULD_SHOW_LAUNCHER flag
+                // (b/114338689) whenever vr 2d display id is set.
+                || mDisplayId == mService.mVr2dDisplayId
+                || mService.mForceDesktopModeOnExternalDisplays;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplaySettings.java b/services/core/java/com/android/server/wm/DisplaySettings.java
index 44956ab..624fbc7 100644
--- a/services/core/java/com/android/server/wm/DisplaySettings.java
+++ b/services/core/java/com/android/server/wm/DisplaySettings.java
@@ -185,15 +185,12 @@
         }
         // No record is present so use default windowing mode policy.
         if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
-            if (displayId == Display.DEFAULT_DISPLAY) {
-                windowingMode = (mService.mIsPc && mService.mSupportsFreeformWindowManagement)
-                        ? WindowConfiguration.WINDOWING_MODE_FREEFORM
-                        : WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-            } else {
-                windowingMode = mService.mSupportsFreeformWindowManagement
-                        ? WindowConfiguration.WINDOWING_MODE_FREEFORM
-                        : WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-            }
+            final boolean forceDesktopMode = mService.mForceDesktopModeOnExternalDisplays
+                    && displayId != Display.DEFAULT_DISPLAY;
+            windowingMode = mService.mSupportsFreeformWindowManagement
+                    && (mService.mIsPc || forceDesktopMode)
+                    ? WindowConfiguration.WINDOWING_MODE_FREEFORM
+                    : WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
         }
         return windowingMode;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayWindowController.java b/services/core/java/com/android/server/wm/DisplayWindowController.java
index f772216..f4f4dd3 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowController.java
@@ -314,6 +314,17 @@
                 || transit == TRANSIT_TASK_TO_FRONT;
     }
 
+    /**
+     * Checks if system decorations should be shown on this display.
+     *
+     * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
+     */
+    public boolean supportsSystemDecorations() {
+        synchronized (mGlobalLock) {
+            return mContainer.supportsSystemDecorations();
+        }
+    }
+
     @Override
     public String toString() {
         return "{DisplayWindowController displayId=" + mDisplayId + "}";
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a641f75..73cdbd3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -29,6 +29,7 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.myPid;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.DOCKED_INVALID;
@@ -538,12 +539,21 @@
     int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
     Rect mDockedStackCreateBounds;
 
-    boolean mForceResizableTasks = false;
-    boolean mSupportsPictureInPicture = false;
-    boolean mSupportsFreeformWindowManagement = false;
-    boolean mIsPc = false;
+    boolean mForceResizableTasks;
+    boolean mSupportsPictureInPicture;
+    boolean mSupportsFreeformWindowManagement;
+    boolean mIsPc;
+    /**
+     * Flag that indicates that desktop mode is forced for public secondary screens.
+     *
+     * This includes several settings:
+     * - Set freeform windowing mode on external screen if it's supported and enabled.
+     * - Enable system decorations and IME on external screen.
+     * - TODO: Show mouse pointer on external screen.
+     */
+    boolean mForceDesktopModeOnExternalDisplays;
 
-    boolean mDisableTransitionAnimation = false;
+    boolean mDisableTransitionAnimation;
 
     int getDragLayerLocked() {
         return mPolicy.getWindowLayerFromTypeLw(TYPE_DRAG) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
@@ -976,17 +986,21 @@
             }
         }, UserHandle.ALL, suspendPackagesFilter, null, null);
 
+        final ContentResolver resolver = context.getContentResolver();
         // Get persisted window scale setting
-        mWindowAnimationScaleSetting = Settings.Global.getFloat(context.getContentResolver(),
+        mWindowAnimationScaleSetting = Settings.Global.getFloat(resolver,
                 Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
-        mTransitionAnimationScaleSetting = Settings.Global.getFloat(context.getContentResolver(),
+        mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
                 Settings.Global.TRANSITION_ANIMATION_SCALE,
                 context.getResources().getFloat(
                         R.dimen.config_appTransitionAnimationDurationScaleDefault));
 
-        setAnimatorDurationScale(Settings.Global.getFloat(context.getContentResolver(),
+        setAnimatorDurationScale(Settings.Global.getFloat(resolver,
                 Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting));
 
+        mForceDesktopModeOnExternalDisplays = Settings.Global.getInt(resolver,
+                DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
+
         IntentFilter filter = new IntentFilter();
         // Track changes to DevicePolicyManager state so we can enable/disable keyguard.
         filter.addAction(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
@@ -6406,6 +6420,12 @@
         }
     }
 
+    void setForceDesktopModeOnExternalDisplays(boolean forceDesktopModeOnExternalDisplays) {
+        synchronized (mWindowMap) {
+            mForceDesktopModeOnExternalDisplays = forceDesktopModeOnExternalDisplays;
+        }
+    }
+
     public void setIsPc(boolean isPc) {
         synchronized (mGlobalLock) {
             mIsPc = isPc;
@@ -6584,10 +6604,10 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                final DisplayContent dc = getDisplayContentOrCreate(displayId, null);
+                final DisplayContent dc = getDisplayContentOrCreate(displayId, null /* token */);
                 if (dc == null) {
                     throw new IllegalArgumentException(
-                            "Trying to register a non existent display.");
+                            "Trying to configure a non existent display.");
                 }
                 // We usually set the override info in DisplayManager so that we get consistent
                 // values when displays are changing. However, we don't do this for displays that
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java
index f0c49c8..5808bc9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java
@@ -64,6 +64,7 @@
 
         mWm.setSupportsFreeformWindowManagement(false);
         mWm.setIsPc(false);
+        mWm.setForceDesktopModeOnExternalDisplays(false);
 
         mTarget = new DisplaySettings(mWm, TEST_FOLDER);
 
@@ -78,7 +79,7 @@
     }
 
     @Test
-    public void testPrimaryDisplayDefaultToFullscreenWithoutFreeformSupport() {
+    public void testPrimaryDisplayDefaultToFullscreen_NoFreeformSupport() {
         mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
 
         assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
@@ -86,7 +87,7 @@
     }
 
     @Test
-    public void testPrimaryDisplayDefaultToFullscreenWithFreeformSupportNonPc() {
+    public void testPrimaryDisplayDefaultToFullscreen_HasFreeformSupport_NonPc_NoDesktopMode() {
         mWm.setSupportsFreeformWindowManagement(true);
 
         mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
@@ -96,7 +97,18 @@
     }
 
     @Test
-    public void testPrimaryDisplayDefaultToFreeformWithFreeformIsPc() {
+    public void testPrimaryDisplayDefaultToFullscreen_HasFreeformSupport_NonPc_HasDesktopMode() {
+        mWm.setSupportsFreeformWindowManagement(true);
+        mWm.setForceDesktopModeOnExternalDisplays(true);
+
+        mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
+
+        assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
+                mPrimaryDisplay.getWindowingMode());
+    }
+
+    @Test
+    public void testPrimaryDisplayDefaultToFreeform_HasFreeformSupport_IsPc() {
         mWm.setSupportsFreeformWindowManagement(true);
         mWm.setIsPc(true);
 
@@ -107,7 +119,7 @@
     }
 
     @Test
-    public void testSecondaryDisplayDefaultToFullscreenWithoutFreeformSupport() {
+    public void testSecondaryDisplayDefaultToFullscreen_NoFreeformSupport() {
         mTarget.applySettingsToDisplayLocked(mSecondaryDisplay);
 
         assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
@@ -115,17 +127,28 @@
     }
 
     @Test
-    public void testSecondaryDisplayDefaultToFreeformWithFreeformSupportNonPc() {
+    public void testSecondaryDisplayDefaultToFreeform_HasFreeformSupport_NonPc_NoDesktopMode() {
         mWm.setSupportsFreeformWindowManagement(true);
 
         mTarget.applySettingsToDisplayLocked(mSecondaryDisplay);
 
+        assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
+                mSecondaryDisplay.getWindowingMode());
+    }
+
+    @Test
+    public void testSecondaryDisplayDefaultToFreeform_HasFreeformSupport_NonPc_HasDesktopMode() {
+        mWm.setSupportsFreeformWindowManagement(true);
+        mWm.setForceDesktopModeOnExternalDisplays(true);
+
+        mTarget.applySettingsToDisplayLocked(mSecondaryDisplay);
+
         assertEquals(WindowConfiguration.WINDOWING_MODE_FREEFORM,
                 mSecondaryDisplay.getWindowingMode());
     }
 
     @Test
-    public void testSecondaryDisplayDefaultToFreeformWithFreeformSupportIsPc() {
+    public void testSecondaryDisplayDefaultToFreeform_HasFreeformSupport_IsPc() {
         mWm.setSupportsFreeformWindowManagement(true);
         mWm.setIsPc(true);