Merge "Foldables: Implement emulation for folded screen"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c05795d..1db8135 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -861,6 +861,11 @@
          The default is false. -->
     <bool name="config_lidControlsSleep">false</bool>
 
+    <!-- Indicate whether closing the lid causes the device to enter the folded state which means
+         to get a smaller screen and opening the lid causes the device to enter the unfolded state
+         which means to get a larger screen. -->
+    <bool name="config_lidControlsDisplayFold">false</bool>
+
     <!-- Desk dock behavior -->
 
     <!-- The number of degrees to rotate the display when the device is in a desk dock.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 47bd9da..aefa9df 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3532,6 +3532,7 @@
   <java-symbol type="integer" name="config_defaultRingVibrationIntensity" />
 
   <java-symbol type="bool" name="config_maskMainBuiltInDisplayCutout" />
+  <java-symbol type="bool" name="config_lidControlsDisplayFold" />
 
   <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" />
   <java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" />
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 9566598..7414e55 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -418,10 +418,15 @@
         // Now add back the offset for the masked area.
         mTempDisplayRect.offset(maskingInsets.left, maskingInsets.top);
 
-        mTempDisplayRect.left += mDisplayOffsetX;
-        mTempDisplayRect.right += mDisplayOffsetX;
-        mTempDisplayRect.top += mDisplayOffsetY;
-        mTempDisplayRect.bottom += mDisplayOffsetY;
+        if (orientation == Surface.ROTATION_0) {
+            mTempDisplayRect.offset(mDisplayOffsetX, mDisplayOffsetY);
+        } else if (orientation == Surface.ROTATION_90) {
+            mTempDisplayRect.offset(mDisplayOffsetY, -mDisplayOffsetX);
+        } else if (orientation == Surface.ROTATION_180) {
+            mTempDisplayRect.offset(-mDisplayOffsetX, -mDisplayOffsetY);
+        } else {  // Surface.ROTATION_270
+            mTempDisplayRect.offset(-mDisplayOffsetY, mDisplayOffsetX);
+        }
         device.setProjectionLocked(t, orientation, mTempLayerStackRect, mTempDisplayRect);
     }
 
diff --git a/services/core/java/com/android/server/policy/DisplayFoldController.java b/services/core/java/com/android/server/policy/DisplayFoldController.java
new file mode 100644
index 0000000..71d6dfc
--- /dev/null
+++ b/services/core/java/com/android/server/policy/DisplayFoldController.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 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.policy;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.view.DisplayInfo;
+
+import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
+
+/**
+ * Controls the behavior of foldable devices whose screen can literally bend and fold.
+ */
+class DisplayFoldController {
+
+    private static final String TAG = "DisplayFoldController";
+    private final WindowManagerInternal mWindowManagerInternal;
+    private final DisplayManagerInternal mDisplayManagerInternal;
+    private final int mDisplayId;
+
+    /** The display area while device is folded. */
+    private final Rect mFoldedArea;
+
+    private final DisplayInfo mNonOverrideDisplayInfo = new DisplayInfo();
+    private Boolean mFolded;
+
+    DisplayFoldController(WindowManagerInternal windowManagerInternal,
+            DisplayManagerInternal displayManagerInternal, int displayId, Rect foldedArea) {
+        mWindowManagerInternal = windowManagerInternal;
+        mDisplayManagerInternal = displayManagerInternal;
+        mDisplayId = displayId;
+        mFoldedArea = new Rect(foldedArea);
+    }
+
+    void setDeviceFolded(boolean folded) {
+        if (mFolded != null && mFolded == folded) {
+            return;
+        }
+        if (folded) {
+            mDisplayManagerInternal.getNonOverrideDisplayInfo(mDisplayId, mNonOverrideDisplayInfo);
+            final int dx = (mNonOverrideDisplayInfo.logicalWidth - mFoldedArea.width()) / 2
+                    - mFoldedArea.left;
+            final int dy = (mNonOverrideDisplayInfo.logicalHeight - mFoldedArea.height()) / 2
+                    - mFoldedArea.top;
+
+            mWindowManagerInternal.setForcedDisplaySize(mDisplayId, mFoldedArea.width(),
+                    mFoldedArea.height());
+            mDisplayManagerInternal.setDisplayOffsets(mDisplayId, -dx, -dy);
+        } else {
+            mWindowManagerInternal.clearForcedDisplaySize(mDisplayId);
+            mDisplayManagerInternal.setDisplayOffsets(mDisplayId, 0, 0);
+        }
+        mFolded = folded;
+    }
+
+    /**
+     * Only used for the case that persist.debug.force_foldable is set.
+     * This is using proximity sensor to simulate the fold state switch.
+     */
+    static DisplayFoldController createWithProxSensor(Context context, int displayId) {
+        final SensorManager sensorManager = context.getSystemService(SensorManager.class);
+        final Sensor proxSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+        if (proxSensor == null) {
+            return null;
+        }
+
+        final DisplayFoldController result = create(displayId);
+        sensorManager.registerListener(new SensorEventListener() {
+            @Override
+            public void onSensorChanged(SensorEvent event) {
+                result.setDeviceFolded(event.values[0] < 1f);
+            }
+
+            @Override
+            public void onAccuracyChanged(Sensor sensor, int accuracy) {
+                // Ignore.
+            }
+        }, proxSensor, SensorManager.SENSOR_DELAY_NORMAL);
+
+        return result;
+    }
+
+    static DisplayFoldController create(int displayId) {
+        final DisplayManagerInternal displayService =
+                LocalServices.getService(DisplayManagerInternal.class);
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayService.getNonOverrideDisplayInfo(displayId, displayInfo);
+        final Rect foldedArea = new Rect(0, displayInfo.logicalHeight / 2,
+                displayInfo.logicalWidth, displayInfo.logicalHeight);
+
+        return new DisplayFoldController(LocalServices.getService(WindowManagerInternal.class),
+                displayService, displayId, foldedArea);
+    }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 13c4d88..25cfc72 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -374,6 +374,7 @@
     SearchManager mSearchManager;
     AccessibilityManager mAccessibilityManager;
     BurnInProtectionHelper mBurnInProtectionHelper;
+    private DisplayFoldController mDisplayFoldController;
     AppOpsManager mAppOpsManager;
     private ScreenshotHelper mScreenshotHelper;
     private boolean mHasFeatureWatch;
@@ -471,6 +472,7 @@
     int mLidNavigationAccessibility;
     boolean mLidControlsScreenLock;
     boolean mLidControlsSleep;
+    private boolean mLidControlsDisplayFold;
     int mShortPressOnPowerBehavior;
     int mLongPressOnPowerBehavior;
     int mVeryLongPressOnPowerBehavior;
@@ -1794,6 +1796,8 @@
                 com.android.internal.R.bool.config_lidControlsScreenLock);
         mLidControlsSleep = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_lidControlsSleep);
+        mLidControlsDisplayFold = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_lidControlsDisplayFold);
 
         mAllowTheaterModeWakeFromKey = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromKey);
@@ -1850,6 +1854,13 @@
 
         readConfigurationDependentBehaviors();
 
+        if (mLidControlsDisplayFold) {
+            mDisplayFoldController = DisplayFoldController.create(DEFAULT_DISPLAY);
+        } else if (SystemProperties.getBoolean("persist.debug.force_foldable", false)) {
+            mDisplayFoldController = DisplayFoldController.createWithProxSensor(context,
+                    DEFAULT_DISPLAY);
+        }
+
         mAccessibilityManager = (AccessibilityManager) context.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
 
@@ -4972,7 +4983,9 @@
 
     private void applyLidSwitchState() {
         final int lidState = mDefaultDisplayPolicy.getLidState();
-        if (lidState == LID_CLOSED && mLidControlsSleep) {
+        if (mLidControlsDisplayFold && mDisplayFoldController != null) {
+            mDisplayFoldController.setDeviceFolded(lidState == LID_CLOSED);
+        } else if (lidState == LID_CLOSED && mLidControlsSleep) {
             goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_LID_SWITCH,
                     PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
         } else if (lidState == LID_CLOSED && mLidControlsScreenLock) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index e204697..33e46f4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -313,6 +313,22 @@
     public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout);
 
     /**
+     * Overrides the display size.
+     *
+     * @param displayId The display to override the display size.
+     * @param width The width to override.
+     * @param height The height to override.
+     */
+    public abstract void setForcedDisplaySize(int displayId, int width, int height);
+
+    /**
+     * Recover the display size to real display size.
+     *
+     * @param displayId The display to recover the display size.
+     */
+    public abstract void clearForcedDisplaySize(int displayId);
+
+    /**
      * Adds a window token for a given window type.
      *
      * @param token The token to add.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e6581df..12b858c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6937,6 +6937,16 @@
         }
 
         @Override
+        public void setForcedDisplaySize(int displayId, int width, int height) {
+            WindowManagerService.this.setForcedDisplaySize(displayId, width, height);
+        }
+
+        @Override
+        public void clearForcedDisplaySize(int displayId) {
+            WindowManagerService.this.clearForcedDisplaySize(displayId);
+        }
+
+        @Override
         public void addWindowToken(IBinder token, int type, int displayId) {
             WindowManagerService.this.addWindowToken(token, type, displayId);
         }
diff --git a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
index ea3f758..98bad93 100644
--- a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
+++ b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
@@ -170,4 +170,9 @@
     public int hashCode() {
         return Objects.hash(mInner, mFrameSize);
     }
+
+    @Override
+    public String toString() {
+        return "WmDisplayCutout{" + mInner + ", mFrameSize=" + mFrameSize + '}';
+    }
 }