Bootstrap freeform external displays.

To get freeform external display one can:
1. Enable freeform windows in developer options;
2. Reboot the device;
3. Connect an external device.

All tasks launched in the external display are then launched in freeform
mode by default, and centered in that display.

There are a lot to do after this CL. It needs caption and window decor
so it can be maximized, restored, drag-moved and drag-resized. There is
no system UI on external display yet so we can't launch arbitrary
activities on it, nor can we minimize anything.

I'm using ActivityView for testing and it uses input forwarder, but we
need to solve the use of mouse and keyboard when it goes to a real
external display.

In addition, tightened the visibility of DisplaySettings class.

Bug: 111840884
Test: go/wm-smoke
Test: activity launching on external freeform displays.
Test: atest DisplaySettingsTests
Change-Id: Ie2a05110ada60b054ac35dae948ad309ea378b1c
diff --git a/services/core/java/com/android/server/am/ActivityTaskManagerService.java b/services/core/java/com/android/server/am/ActivityTaskManagerService.java
index 3b3263c..bd502df 100644
--- a/services/core/java/com/android/server/am/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityTaskManagerService.java
@@ -27,6 +27,7 @@
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.Manifest.permission.STOP_APP_SWITCHES;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
+import static android.content.pm.PackageManager.FEATURE_PC;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.provider.Settings.Global.HIDE_ERROR_DIALOGS;
 import static android.provider.Settings.System.FONT_SCALE;
@@ -541,6 +542,7 @@
         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 isPc = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
 
         // Transfer any global setting for forcing RTL layout, into a System Property
         SystemProperties.set(DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
@@ -573,6 +575,8 @@
             }
             mWindowManager.setForceResizableTasks(mForceResizableActivities);
             mWindowManager.setSupportsPictureInPicture(mSupportsPictureInPicture);
+            mWindowManager.setSupportsFreeformWindowManagement(mSupportsFreeformWindowManagement);
+            mWindowManager.setIsPc(isPc);
             // This happens before any activities are started, so we can change global configuration
             // in-place.
             updateConfigurationLocked(configuration, null, true);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6286062..cb19abd 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1318,9 +1318,7 @@
         final int dw = displayInfo.logicalWidth;
         final int dh = displayInfo.logicalHeight;
         config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
-        // TODO: Probably best to set this based on some setting in the display content object,
-        // so the display can be configured for things like fullscreen.
-        config.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        config.windowConfiguration.setWindowingMode(getWindowingMode());
 
         final float density = mDisplayMetrics.density;
         config.screenWidthDp =
diff --git a/services/core/java/com/android/server/wm/DisplaySettings.java b/services/core/java/com/android/server/wm/DisplaySettings.java
index 97b64dc..bbb690f 100644
--- a/services/core/java/com/android/server/wm/DisplaySettings.java
+++ b/services/core/java/com/android/server/wm/DisplaySettings.java
@@ -19,12 +19,19 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.graphics.Rect;
 import android.os.Environment;
+import android.provider.Settings;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
+import android.view.Display;
+import android.view.DisplayInfo;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
 
@@ -43,37 +50,48 @@
 /**
  * Current persistent settings about a display
  */
-public class DisplaySettings {
+class DisplaySettings {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplaySettings" : TAG_WM;
 
+    private final WindowManagerService mService;
     private final AtomicFile mFile;
     private final HashMap<String, Entry> mEntries = new HashMap<String, Entry>();
 
-    public static class Entry {
-        public final String name;
-        public int overscanLeft;
-        public int overscanTop;
-        public int overscanRight;
-        public int overscanBottom;
+    private static class Entry {
+        private final String name;
+        private int overscanLeft;
+        private int overscanTop;
+        private int overscanRight;
+        private int overscanBottom;
+        private int windowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
-        public Entry(String _name) {
+        private Entry(String _name) {
             name = _name;
         }
     }
 
-    public DisplaySettings() {
-        File dataDir = Environment.getDataDirectory();
-        File systemDir = new File(dataDir, "system");
-        mFile = new AtomicFile(new File(systemDir, "display_settings.xml"), "wm-displays");
+    DisplaySettings(WindowManagerService service) {
+        this(service, new File(Environment.getDataDirectory(), "system"));
     }
 
-    public void getOverscanLocked(String name, String uniqueId, Rect outRect) {
+    @VisibleForTesting
+    DisplaySettings(WindowManagerService service, File folder) {
+        mService = service;
+        mFile = new AtomicFile(new File(folder, "display_settings.xml"), "wm-displays");
+    }
+
+    private Entry getEntry(String name, String uniqueId) {
         // Try to get the entry with the unique if possible.
         // Else, fall back on the display name.
         Entry entry;
         if (uniqueId == null || (entry = mEntries.get(uniqueId)) == null) {
             entry = mEntries.get(name);
         }
+        return entry;
+    }
+
+    private void getOverscanLocked(String name, String uniqueId, Rect outRect) {
+        final Entry entry = getEntry(name, uniqueId);
         if (entry != null) {
             outRect.left = entry.overscanLeft;
             outRect.top = entry.overscanTop;
@@ -84,7 +102,7 @@
         }
     }
 
-    public void setOverscanLocked(String uniqueId, String name, int left, int top, int right,
+    void setOverscanLocked(String uniqueId, String name, int left, int top, int right,
             int bottom) {
         if (left == 0 && top == 0 && right == 0 && bottom == 0) {
             // Right now all we are storing is overscan; if there is no overscan,
@@ -105,7 +123,47 @@
         entry.overscanBottom = bottom;
     }
 
-    public void readSettingsLocked() {
+    private int getWindowingModeLocked(String name, String uniqueId, int displayId) {
+        final Entry entry = getEntry(name, uniqueId);
+        int windowingMode = entry != null ? entry.windowingMode
+                : WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+        // This display used to be in freeform, but we don't support freeform anymore, so fall
+        // back to fullscreen.
+        if (windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
+                && !mService.mSupportsFreeformWindowManagement) {
+            return WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+        }
+        // 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;
+            }
+        }
+        return windowingMode;
+    }
+
+    void applySettingsToDisplayLocked(DisplayContent dc) {
+        final DisplayInfo displayInfo = dc.getDisplayInfo();
+
+        // Setting windowing mode first, because it may override overscan values later.
+        dc.setWindowingMode(getWindowingModeLocked(displayInfo.name, displayInfo.uniqueId,
+                dc.getDisplayId()));
+
+        final Rect rect = new Rect();
+        getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect);
+        displayInfo.overscanLeft = rect.left;
+        displayInfo.overscanTop = rect.top;
+        displayInfo.overscanRight = rect.right;
+        displayInfo.overscanBottom = rect.bottom;
+    }
+
+    void readSettingsLocked() {
         FileInputStream stream;
         try {
             stream = mFile.openRead();
@@ -169,11 +227,15 @@
     }
 
     private int getIntAttribute(XmlPullParser parser, String name) {
+        return getIntAttribute(parser, name, 0 /* defaultValue */);
+    }
+
+    private int getIntAttribute(XmlPullParser parser, String name, int defaultValue) {
         try {
             String str = parser.getAttributeValue(null, name);
-            return str != null ? Integer.parseInt(str) : 0;
+            return str != null ? Integer.parseInt(str) : defaultValue;
         } catch (NumberFormatException e) {
-            return 0;
+            return defaultValue;
         }
     }
 
@@ -186,12 +248,14 @@
             entry.overscanTop = getIntAttribute(parser, "overscanTop");
             entry.overscanRight = getIntAttribute(parser, "overscanRight");
             entry.overscanBottom = getIntAttribute(parser, "overscanBottom");
+            entry.windowingMode = getIntAttribute(parser, "windowingMode",
+                    WindowConfiguration.WINDOWING_MODE_UNDEFINED);
             mEntries.put(name, entry);
         }
         XmlUtils.skipCurrentTag(parser);
     }
 
-    public void writeSettingsLocked() {
+    void writeSettingsLocked() {
         FileOutputStream stream;
         try {
             stream = mFile.startWrite();
@@ -221,6 +285,9 @@
                 if (entry.overscanBottom != 0) {
                     out.attribute(null, "overscanBottom", Integer.toString(entry.overscanBottom));
                 }
+                if (entry.windowingMode != WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
+                    out.attribute(null, "windowingMode", Integer.toString(entry.windowingMode));
+                }
                 out.endTag(null, "display");
             }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3df4eb7..6ba5aef 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -221,16 +221,11 @@
 
         if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display);
 
-        final DisplayInfo displayInfo = dc.getDisplayInfo();
-        final Rect rect = new Rect();
-        mService.mDisplaySettings.getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect);
-        displayInfo.overscanLeft = rect.left;
-        displayInfo.overscanTop = rect.top;
-        displayInfo.overscanRight = rect.right;
-        displayInfo.overscanBottom = rect.bottom;
+        mService.mDisplaySettings.applySettingsToDisplayLocked(dc);
+
         if (mService.mDisplayManagerInternal != null) {
             mService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
-                    displayId, displayInfo);
+                    displayId, dc.getDisplayInfo());
             dc.configureDisplayPolicy();
 
             // Tap Listeners are supported for:
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f4035e0..e4caf53 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -565,6 +565,8 @@
 
     boolean mForceResizableTasks = false;
     boolean mSupportsPictureInPicture = false;
+    boolean mSupportsFreeformWindowManagement = false;
+    boolean mIsPc = false;
 
     boolean mDisableTransitionAnimation = false;
 
@@ -953,7 +955,7 @@
                 com.android.internal.R.bool.config_disableTransitionAnimation);
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
-        mDisplaySettings = new DisplaySettings();
+        mDisplaySettings = new DisplaySettings(this);
         mDisplaySettings.readSettingsLocked();
 
         mPolicy = policy;
@@ -6922,6 +6924,18 @@
         }
     }
 
+    public void setSupportsFreeformWindowManagement(boolean supportsFreeformWindowManagement) {
+        synchronized (mWindowMap) {
+            mSupportsFreeformWindowManagement = supportsFreeformWindowManagement;
+        }
+    }
+
+    public void setIsPc(boolean isPc) {
+        synchronized (mWindowMap) {
+            mIsPc = isPc;
+        }
+    }
+
     static int dipToPixel(int dip, DisplayMetrics displayMetrics) {
         return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics);
     }