Add special mirroring modes for demonstration purposes.

Assume rotation of HDMI display is portait.
$ adb shell setprop persist.demo.hdmirotation portrait

Don't lock rotation while HDMI is plugged in.
$ adb shell setprop persist.demo.hdmirotationlock false

Hide secondary displays from apps but continue mirroring to them.
$ adb shell setprop persist.demo.singledisplay true

Bug: 7326281
Change-Id: I8f9a3b0bc19821a3a01043b0f516806dac82ce53
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 91d5eaa..24ce9bc 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -299,6 +299,7 @@
     int mCarDockRotation;
     int mDeskDockRotation;
     int mHdmiRotation;
+    boolean mHdmiRotationLock;
 
     int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
     int mUserRotation = Surface.ROTATION_0;
@@ -1035,11 +1036,14 @@
             mCanHideNavigationBar = false;
         }
 
+        // For demo purposes, allow the rotation of the HDMI display to be controlled.
+        // By default, HDMI locks rotation to landscape.
         if ("portrait".equals(SystemProperties.get("persist.demo.hdmirotation"))) {
             mHdmiRotation = mPortraitRotation;
         } else {
             mHdmiRotation = mLandscapeRotation;
         }
+        mHdmiRotationLock = SystemProperties.getBoolean("persist.demo.hdmirotationlock", true);
     }
 
     public void updateSettings() {
@@ -3873,7 +3877,7 @@
                 // enable 180 degree rotation while docked.
                 preferredRotation = mDeskDockEnablesAccelerometer
                         ? sensorRotation : mDeskDockRotation;
-            } else if (mHdmiPlugged) {
+            } else if (mHdmiPlugged && mHdmiRotationLock) {
                 // Ignore sensor when plugged into HDMI.
                 // Note that the dock orientation overrides the HDMI orientation.
                 preferredRotation = mHdmiRotation;
@@ -4538,5 +4542,7 @@
                 pw.print(" mSeascapeRotation="); pw.println(mSeascapeRotation);
         pw.print(prefix); pw.print("mPortraitRotation="); pw.print(mPortraitRotation);
                 pw.print(" mUpsideDownRotation="); pw.println(mUpsideDownRotation);
+        pw.print(prefix); pw.print("mHdmiRotation="); pw.print(mHdmiRotation);
+                pw.print(" mHdmiRotationLock="); pw.println(mHdmiRotationLock);
     }
 }
diff --git a/services/java/com/android/server/display/DisplayDeviceInfo.java b/services/java/com/android/server/display/DisplayDeviceInfo.java
index b4dab86..e76bf44 100644
--- a/services/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/java/com/android/server/display/DisplayDeviceInfo.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import android.util.DisplayMetrics;
+import android.view.Surface;
 
 import libcore.util.Objects;
 
@@ -31,11 +32,21 @@
     public static final int FLAG_DEFAULT_DISPLAY = 1 << 0;
 
     /**
-     * Flag: Indicates that this display device can rotate to show contents in a
-     * different orientation.  Otherwise the rotation is assumed to be fixed in the
-     * natural orientation and the display manager should transform the content to fit.
+     * Flag: Indicates that the orientation of this display device is coupled to the
+     * rotation of its associated logical display.
+     * <p>
+     * This flag should be applied to the default display to indicate that the user
+     * physically rotates the display when content is presented in a different orientation.
+     * The display manager will apply a coordinate transformation assuming that the
+     * physical orientation of the display matches the logical orientation of its content.
+     * </p><p>
+     * The flag should not be set when the display device is mounted in a fixed orientation
+     * such as on a desk.  The display manager will apply a coordinate transformation
+     * such as a scale and translation to letterbox or pillarbox format under the
+     * assumption that the physical orientation of the display is invariant.
+     * </p>
      */
-    public static final int FLAG_SUPPORTS_ROTATION = 1 << 1;
+    public static final int FLAG_ROTATES_WITH_CONTENT = 1 << 1;
 
     /**
      * Flag: Indicates that this display device has secure video output, such as HDCP.
@@ -116,6 +127,17 @@
      */
     public int touch;
 
+    /**
+     * The additional rotation to apply to all content presented on the display device
+     * relative to its physical coordinate system.  Default is {@link Surface#ROTATION_0}.
+     * <p>
+     * This field can be used to compensate for the fact that the display has been
+     * physically rotated relative to its natural orientation such as an HDMI monitor
+     * that has been mounted sideways to appear to be portrait rather than landscape.
+     * </p>
+     */
+    public int rotation = Surface.ROTATION_0;
+
     public void setAssumedDensityForExternalDisplay(int width, int height) {
         densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080;
         // Technically, these values should be smaller than the apparent density
@@ -139,7 +161,8 @@
                 && xDpi == other.xDpi
                 && yDpi == other.yDpi
                 && flags == other.flags
-                && touch == other.touch;
+                && touch == other.touch
+                && rotation == other.rotation;
     }
 
     @Override
@@ -157,14 +180,18 @@
         yDpi = other.yDpi;
         flags = other.flags;
         touch = other.touch;
+        rotation = other.rotation;
     }
 
     // For debugging purposes
     @Override
     public String toString() {
-        return "DisplayDeviceInfo{\"" + name + "\": " + width + " x " + height + ", " + refreshRate + " fps, "
+        return "DisplayDeviceInfo{\"" + name + "\": " + width + " x " + height + ", "
+                + refreshRate + " fps, "
                 + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi"
-                + ", touch " + touchToString(touch) + flagsToString(flags) + "}";
+                + ", touch " + touchToString(touch) + flagsToString(flags)
+                + ", rotation " + rotation
+                + "}";
     }
 
     private static String touchToString(int touch) {
@@ -185,8 +212,8 @@
         if ((flags & FLAG_DEFAULT_DISPLAY) != 0) {
             msg.append(", FLAG_DEFAULT_DISPLAY");
         }
-        if ((flags & FLAG_SUPPORTS_ROTATION) != 0) {
-            msg.append(", FLAG_SUPPORTS_ROTATION");
+        if ((flags & FLAG_ROTATES_WITH_CONTENT) != 0) {
+            msg.append(", FLAG_ROTATES_WITH_CONTENT");
         }
         if ((flags & FLAG_SECURE) != 0) {
             msg.append(", FLAG_SECURE");
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index 93896af..e58a0a5cb 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -127,6 +127,13 @@
     // services should be started.  This option may disable certain display adapters.
     public boolean mOnlyCore;
 
+    // True if the display manager service should pretend there is only one display
+    // and only tell applications about the existence of the default logical display.
+    // The display manager can still mirror content to secondary displays but applications
+    // cannot present unique content on those displays.
+    // Used for demonstration purposes only.
+    private final boolean mSingleDisplayDemoMode;
+
     // All callback records indexed by calling process id.
     public final SparseArray<CallbackRecord> mCallbacks =
             new SparseArray<CallbackRecord>();
@@ -182,6 +189,7 @@
         mHandler = new DisplayManagerHandler(mainHandler.getLooper());
         mUiHandler = uiHandler;
         mDisplayAdapterListener = new DisplayAdapterListener();
+        mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
 
         mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER);
     }
@@ -631,6 +639,12 @@
             isDefault = false;
         }
 
+        if (!isDefault && mSingleDisplayDemoMode) {
+            Slog.i(TAG, "Not creating a logical display for a secondary display "
+                    + " because single display demo mode is enabled: " + deviceInfo);
+            return;
+        }
+
         final int displayId = assignDisplayIdLocked(isDefault);
         final int layerStack = assignLayerStackLocked(displayId);
 
@@ -857,6 +871,7 @@
             pw.println("  mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
             pw.println("  mDefaultViewport=" + mDefaultViewport);
             pw.println("  mExternalTouchViewport=" + mExternalTouchViewport);
+            pw.println("  mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
 
             IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
             ipw.increaseIndent();
diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java
index d780006..fe38d7f 100644
--- a/services/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/java/com/android/server/display/LocalDisplayAdapter.java
@@ -20,6 +20,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.SystemProperties;
 import android.util.SparseArray;
 import android.view.DisplayEventReceiver;
 import android.view.Surface;
@@ -135,7 +136,7 @@
                     mInfo.name = getContext().getResources().getString(
                             com.android.internal.R.string.display_manager_built_in_display_name);
                     mInfo.flags |= DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
-                            | DisplayDeviceInfo.FLAG_SUPPORTS_ROTATION;
+                            | DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
                     mInfo.densityDpi = (int)(mPhys.density * 160 + 0.5f);
                     mInfo.xDpi = mPhys.xDpi;
                     mInfo.yDpi = mPhys.yDpi;
@@ -145,6 +146,12 @@
                             com.android.internal.R.string.display_manager_hdmi_display_name);
                     mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
                     mInfo.setAssumedDensityForExternalDisplay(mPhys.width, mPhys.height);
+
+                    // For demonstration purposes, allow rotation of the external display.
+                    // In the future we might allow the user to configure this directly.
+                    if ("portrait".equals(SystemProperties.get("persist.demo.hdmirotation"))) {
+                        mInfo.rotation = Surface.ROTATION_270;
+                    }
                 }
             }
             return mInfo;
diff --git a/services/java/com/android/server/display/LogicalDisplay.java b/services/java/com/android/server/display/LogicalDisplay.java
index 680662e..aa7ea82 100644
--- a/services/java/com/android/server/display/LogicalDisplay.java
+++ b/services/java/com/android/server/display/LogicalDisplay.java
@@ -241,10 +241,13 @@
         // is rotated when the contents of the logical display are rendered.
         int orientation = Surface.ROTATION_0;
         if (device == mPrimaryDisplayDevice
-                && (displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_SUPPORTS_ROTATION) != 0) {
+                && (displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) {
             orientation = displayInfo.rotation;
         }
 
+        // Apply the physical rotation of the display device itself.
+        orientation = (orientation + displayDeviceInfo.rotation) % 4;
+
         // Set the frame.
         // The frame specifies the rotated physical coordinates into which the viewport
         // is mapped.  We need to take care to preserve the aspect ratio of the viewport.