Support hide display cutout with display area

- Added a new DisplayArea feature for hide display cutout
- Added a new config for enable/disable hide display cutout
- Resize and shift display areas to exclude the cutout areas.
- Force dark status bar when there is a cutout area at top
- Update bounds & offsets for different rotations.

Bug: 157388722
Test: atest HideDisplayCutoutControllerTest
      atest HideDisplayCutoutOrganizerTest
      atest WMShellTest
Change-Id: Iad2c8b1eff1ea4523f8a7e362c9e851d8594141c
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index bc3e35c..6cc3cd39 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -78,6 +78,12 @@
     public static final int FEATURE_FULLSCREEN_MAGNIFICATION = FEATURE_SYSTEM_FIRST + 5;
 
     /**
+     * Display area for hiding display cutout feature
+     * @hide
+     */
+    public static final int FEATURE_HIDE_DISPLAY_CUTOUT = FEATURE_SYSTEM_FIRST + 6;
+
+    /**
      * The last boundary of display area for system features
      */
     public static final int FEATURE_SYSTEM_LAST = 10_000;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 433a46b..3295df1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4510,4 +4510,7 @@
          activity-level letterboxing (size-compat mode). Therefore this override can control the
          maximum screen area that can be occupied by the app in the letterbox mode. -->
     <item name="config_taskLetterboxAspectRatio" format="float" type="dimen">0.0</item>
+
+    <!-- If true, hide the display cutout with display area -->
+    <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6960fb3..fba431c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4095,4 +4095,6 @@
   <java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
 
   <java-symbol type="dimen" name="config_taskLetterboxAspectRatio" />
+
+  <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java
index 4ba84223..bb9accd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java
@@ -16,8 +16,7 @@
 
 package com.android.wm.shell;
 
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.splitscreen.SplitScreen;
@@ -33,16 +32,19 @@
     private final Optional<SplitScreen> mSplitScreenOptional;
     private final Optional<Pip> mPipOptional;
     private final Optional<OneHanded> mOneHandedOptional;
+    private final Optional<HideDisplayCutout> mHideDisplayCutout;
     private final ShellTaskOrganizer mShellTaskOrganizer;
 
     public ShellDump(ShellTaskOrganizer shellTaskOrganizer,
             Optional<SplitScreen> splitScreenOptional,
             Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional) {
+            Optional<OneHanded> oneHandedOptional,
+            Optional<HideDisplayCutout> hideDisplayCutout) {
         mShellTaskOrganizer = shellTaskOrganizer;
         mSplitScreenOptional = splitScreenOptional;
         mPipOptional = pipOptional;
         mOneHandedOptional = oneHandedOptional;
+        mHideDisplayCutout = hideDisplayCutout;
     }
 
     public void dump(PrintWriter pw) {
@@ -52,5 +54,6 @@
         mPipOptional.ifPresent(pip -> pip.dump(pw));
         mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw));
         mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
+        mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw));
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
new file mode 100644
index 0000000..38e0519
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.hidedisplaycutout;
+
+import android.content.res.Configuration;
+
+import androidx.annotation.NonNull;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface to engage hide display cutout feature.
+ */
+public interface HideDisplayCutout {
+    /**
+     * Notifies {@link Configuration} changed.
+     * @param newConfig
+     */
+    void onConfigurationChanged(Configuration newConfig);
+
+    /**
+     * Dumps hide display cutout status.
+     */
+    void dump(@NonNull PrintWriter pw);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
new file mode 100644
index 0000000..e4e2546
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.hidedisplaycutout;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.SystemProperties;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.wm.shell.common.DisplayController;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages the hide display cutout status.
+ */
+public class HideDisplayCutoutController implements HideDisplayCutout {
+    private static final String TAG = "HideDisplayCutoutController";
+
+    private final Context mContext;
+    private final HideDisplayCutoutOrganizer mOrganizer;
+    @VisibleForTesting
+    boolean mEnabled;
+
+    HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer) {
+        mContext = context;
+        mOrganizer = organizer;
+        updateStatus();
+    }
+
+    /**
+     * Creates {@link HideDisplayCutoutController}, returns {@code null} if the feature is not
+     * supported.
+     */
+    @Nullable
+    public static HideDisplayCutoutController create(
+            Context context, DisplayController displayController) {
+        // The SystemProperty is set for devices that support this feature and is used to control
+        // whether to create the HideDisplayCutout instance.
+        // It's defined in the device.mk (e.g. device/google/crosshatch/device.mk).
+        if (!SystemProperties.getBoolean("ro.support_hide_display_cutout", false)) {
+            return null;
+        }
+
+        HideDisplayCutoutOrganizer organizer =
+                new HideDisplayCutoutOrganizer(context, displayController);
+        return new HideDisplayCutoutController(context, organizer);
+    }
+
+    @VisibleForTesting
+    void updateStatus() {
+        // The config value is used for controlling enabling/disabling status of the feature and is
+        // defined in the config.xml in a "Hide Display Cutout" overlay package (e.g. device/google/
+        // crosshatch/crosshatch/overlay/packages/apps/overlays/NoCutoutOverlay).
+        final boolean enabled = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_hideDisplayCutoutWithDisplayArea);
+        if (enabled == mEnabled) {
+            return;
+        }
+
+        mEnabled = enabled;
+        if (enabled) {
+            mOrganizer.enableHideDisplayCutout();
+        } else {
+            mOrganizer.disableHideDisplayCutout();
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        updateStatus();
+    }
+
+    @Override
+    public void dump(@NonNull PrintWriter pw) {
+        final String prefix = "  ";
+        pw.print(TAG);
+        pw.println(" states: ");
+        pw.print(prefix);
+        pw.print("mEnabled=");
+        pw.println(mEnabled);
+        mOrganizer.dump(pw);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
new file mode 100644
index 0000000..090d227
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.hidedisplaycutout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.RotationUtils;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.window.DisplayAreaAppearedInfo;
+import android.window.DisplayAreaInfo;
+import android.window.DisplayAreaOrganizer;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.R;
+import com.android.wm.shell.common.DisplayChangeController;
+import com.android.wm.shell.common.DisplayController;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Manages the display areas of hide display cutout feature.
+ */
+class HideDisplayCutoutOrganizer extends DisplayAreaOrganizer {
+    private static final String TAG = "HideDisplayCutoutOrganizer";
+
+    private final Context mContext;
+    private final DisplayController mDisplayController;
+
+    @VisibleForTesting
+    @GuardedBy("this")
+    ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaMap = new ArrayMap();
+    // The default display bound in natural orientation.
+    private final Rect mDefaultDisplayBounds = new Rect();
+    @VisibleForTesting
+    final Rect mCurrentDisplayBounds = new Rect();
+    // The default display cutout in natural orientation.
+    private Insets mDefaultCutoutInsets;
+    private Insets mCurrentCutoutInsets;
+    private boolean mIsDefaultPortrait;
+    private int mStatusBarHeight;
+    @VisibleForTesting
+    int mOffsetX;
+    @VisibleForTesting
+    int mOffsetY;
+    @VisibleForTesting
+    int mRotation;
+
+    /**
+     * Handles rotation based on OnDisplayChangingListener callback.
+     */
+    private final DisplayChangeController.OnDisplayChangingListener mRotationController =
+            (display, fromRotation, toRotation, wct) -> {
+                mRotation = toRotation;
+                updateBoundsAndOffsets(true /* enable */);
+                final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                applyAllBoundsAndOffsets(wct, t);
+                // Only apply t here since the server will do the wct.apply when the method
+                // finishes.
+                t.apply();
+            };
+
+    HideDisplayCutoutOrganizer(Context context, DisplayController displayController) {
+        mContext = context;
+        mDisplayController = displayController;
+    }
+
+    @Override
+    public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
+            @NonNull SurfaceControl leash) {
+        if (!addDisplayAreaInfoAndLeashToMap(displayAreaInfo, leash)) {
+            return;
+        }
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+        applyBoundsAndOffsets(displayAreaInfo.token, leash, wct, tx);
+        applyTransaction(wct, tx);
+    }
+
+    @Override
+    public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
+        synchronized (this) {
+            if (!mDisplayAreaMap.containsKey(displayAreaInfo.token)) {
+                Log.w(TAG, "Unrecognized token: " + displayAreaInfo.token);
+                return;
+            }
+
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            applyBoundsAndOffsets(
+                    displayAreaInfo.token, mDisplayAreaMap.get(displayAreaInfo.token), wct, t);
+            applyTransaction(wct, t);
+            mDisplayAreaMap.remove(displayAreaInfo.token);
+        }
+    }
+
+    private void updateDisplayAreaMap(List<DisplayAreaAppearedInfo> displayAreaInfos) {
+        for (int i = 0; i < displayAreaInfos.size(); i++) {
+            final DisplayAreaInfo info = displayAreaInfos.get(i).getDisplayAreaInfo();
+            final SurfaceControl leash = displayAreaInfos.get(i).getLeash();
+            addDisplayAreaInfoAndLeashToMap(info, leash);
+        }
+    }
+
+    @VisibleForTesting
+    boolean addDisplayAreaInfoAndLeashToMap(@NonNull DisplayAreaInfo displayAreaInfo,
+            @NonNull SurfaceControl leash) {
+        synchronized (this) {
+            if (displayAreaInfo.displayId != DEFAULT_DISPLAY) {
+                return false;
+            }
+            if (mDisplayAreaMap.containsKey(displayAreaInfo.token)) {
+                Log.w(TAG, "Already appeared token: " + displayAreaInfo.token);
+                return false;
+            }
+            mDisplayAreaMap.put(displayAreaInfo.token, leash);
+            return true;
+        }
+    }
+
+    /**
+     * Enables hide display cutout.
+     */
+    void enableHideDisplayCutout() {
+        mDisplayController.addDisplayChangingController(mRotationController);
+        final Display display = mDisplayController.getDisplay(DEFAULT_DISPLAY);
+        if (display != null) {
+            mRotation = display.getRotation();
+        }
+        final List<DisplayAreaAppearedInfo> displayAreaInfos =
+                registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+        updateDisplayAreaMap(displayAreaInfos);
+        updateBoundsAndOffsets(true /* enabled */);
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        applyAllBoundsAndOffsets(wct, t);
+        applyTransaction(wct, t);
+    }
+
+    /**
+     * Disables hide display cutout.
+     */
+    void disableHideDisplayCutout() {
+        updateBoundsAndOffsets(false /* enabled */);
+        mDisplayController.removeDisplayChangingController(mRotationController);
+        unregisterOrganizer();
+    }
+
+    @VisibleForTesting
+    Insets getDisplayCutoutInsetsOfNaturalOrientation() {
+        final Display display = mDisplayController.getDisplay(DEFAULT_DISPLAY);
+        if (display == null) {
+            return Insets.NONE;
+        }
+        DisplayCutout cutout = display.getCutout();
+        Insets insets = cutout != null ? Insets.of(cutout.getSafeInsets()) : Insets.NONE;
+        return mRotation != Surface.ROTATION_0
+                ? RotationUtils.rotateInsets(insets, 4 /* total number of rotation */ - mRotation)
+                : insets;
+    }
+
+    @VisibleForTesting
+    Rect getDisplayBoundsOfNaturalOrientation() {
+        Point realSize = new Point(0, 0);
+        final Display display = mDisplayController.getDisplay(DEFAULT_DISPLAY);
+        if (display != null) {
+            display.getRealSize(realSize);
+        }
+        final boolean isDisplaySizeFlipped = isDisplaySizeFlipped();
+        return new Rect(
+                0,
+                0,
+                isDisplaySizeFlipped ? realSize.y : realSize.x,
+                isDisplaySizeFlipped ? realSize.x : realSize.y);
+    }
+
+    private boolean isDisplaySizeFlipped() {
+        return mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270;
+    }
+
+    /**
+     * Updates bounds and offsets according to current state.
+     *
+     * @param enabled whether the hide display cutout feature is enabled.
+     */
+    @VisibleForTesting
+    void updateBoundsAndOffsets(boolean enabled) {
+        if (!enabled) {
+            resetBoundsAndOffsets();
+        } else {
+            initDefaultValuesIfNeeded();
+
+            // Reset to default values.
+            mCurrentDisplayBounds.set(mDefaultDisplayBounds);
+            mOffsetX = 0;
+            mOffsetY = 0;
+
+            // Update bounds and insets according to the rotation.
+            mCurrentCutoutInsets = RotationUtils.rotateInsets(mDefaultCutoutInsets, mRotation);
+            if (isDisplaySizeFlipped()) {
+                mCurrentDisplayBounds.set(
+                        mCurrentDisplayBounds.top,
+                        mCurrentDisplayBounds.left,
+                        mCurrentDisplayBounds.bottom,
+                        mCurrentDisplayBounds.right);
+            }
+            mCurrentDisplayBounds.inset(mCurrentCutoutInsets);
+
+            // Replace the top bound with the max(status bar height, cutout height) if there is
+            // cutout on the top side.
+            mStatusBarHeight = getStatusBarHeight();
+            if (mCurrentCutoutInsets.top != 0) {
+                mCurrentDisplayBounds.top = Math.max(mStatusBarHeight, mCurrentCutoutInsets.top);
+            }
+            mOffsetX = mCurrentDisplayBounds.left;
+            mOffsetY = mCurrentDisplayBounds.top;
+        }
+    }
+
+    private void resetBoundsAndOffsets() {
+        mCurrentDisplayBounds.setEmpty();
+        mOffsetX = 0;
+        mOffsetY = 0;
+    }
+
+    private void initDefaultValuesIfNeeded() {
+        if (!mDefaultDisplayBounds.isEmpty()) {
+            return;
+        }
+        mDefaultDisplayBounds.set(getDisplayBoundsOfNaturalOrientation());
+        mDefaultCutoutInsets = getDisplayCutoutInsetsOfNaturalOrientation();
+        mIsDefaultPortrait = mDefaultDisplayBounds.width() < mDefaultDisplayBounds.height();
+    }
+
+    private void applyAllBoundsAndOffsets(
+            WindowContainerTransaction wct, SurfaceControl.Transaction t) {
+        synchronized (this) {
+            mDisplayAreaMap.forEach((token, leash) -> {
+                applyBoundsAndOffsets(token, leash, wct, t);
+            });
+        }
+    }
+
+    @VisibleForTesting
+    void applyBoundsAndOffsets(WindowContainerToken token, SurfaceControl leash,
+            WindowContainerTransaction wct, SurfaceControl.Transaction t) {
+        wct.setBounds(token, mCurrentDisplayBounds.isEmpty() ? null : mCurrentDisplayBounds);
+        t.setPosition(leash, mOffsetX,  mOffsetY);
+    }
+
+    @VisibleForTesting
+    void applyTransaction(WindowContainerTransaction wct, SurfaceControl.Transaction t) {
+        applyTransaction(wct);
+        t.apply();
+    }
+
+    private int getStatusBarHeight() {
+        final boolean isLandscape =
+                mIsDefaultPortrait ? isDisplaySizeFlipped() : !isDisplaySizeFlipped();
+        return mContext.getResources().getDimensionPixelSize(
+                isLandscape ? R.dimen.status_bar_height_landscape
+                        : R.dimen.status_bar_height_portrait);
+    }
+
+    void dump(@NonNull PrintWriter pw) {
+        final String prefix = "  ";
+        pw.print(TAG);
+        pw.println(" states: ");
+        synchronized (this) {
+            pw.print(prefix);
+            pw.print("mDisplayAreaMap=");
+            pw.println(mDisplayAreaMap);
+        }
+        pw.print(prefix);
+        pw.print("getDisplayBoundsOfNaturalOrientation()=");
+        pw.println(getDisplayBoundsOfNaturalOrientation());
+        pw.print(prefix);
+        pw.print("mDefaultDisplayBounds=");
+        pw.println(mDefaultDisplayBounds);
+        pw.print(prefix);
+        pw.print("mCurrentDisplayBounds=");
+        pw.println(mCurrentDisplayBounds);
+        pw.print(prefix);
+        pw.print("mDefaultCutoutInsets=");
+        pw.println(mDefaultCutoutInsets);
+        pw.print(prefix);
+        pw.print("mCurrentCutoutInsets=");
+        pw.println(mCurrentCutoutInsets);
+        pw.print(prefix);
+        pw.print("mRotation=");
+        pw.println(mRotation);
+        pw.print(prefix);
+        pw.print("mStatusBarHeight=");
+        pw.println(mStatusBarHeight);
+        pw.print(prefix);
+        pw.print("mOffsetX=");
+        pw.println(mOffsetX);
+        pw.print(prefix);
+        pw.print("mOffsetY=");
+        pw.println(mOffsetY);
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 9940ea5..dca2732 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -30,6 +30,7 @@
         "mockito-target-extended-minus-junit4",
         "truth-prebuilt",
         "testables",
+        "platform-test-annotations",
     ],
 
     libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
new file mode 100644
index 0000000..595440f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.hidedisplaycutout;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class HideDisplayCutoutControllerTest {
+    private TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    private HideDisplayCutoutController mHideDisplayCutoutController;
+    @Mock
+    private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mHideDisplayCutoutController = new HideDisplayCutoutController(
+                mContext, mMockDisplayAreaOrganizer);
+    }
+
+    @Test
+    public void testToggleHideDisplayCutout_On() {
+        mHideDisplayCutoutController.mEnabled = false;
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_hideDisplayCutoutWithDisplayArea, true);
+        reset(mMockDisplayAreaOrganizer);
+        mHideDisplayCutoutController.updateStatus();
+        verify(mMockDisplayAreaOrganizer).enableHideDisplayCutout();
+    }
+
+    @Test
+    public void testToggleHideDisplayCutout_Off() {
+        mHideDisplayCutoutController.mEnabled = true;
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_hideDisplayCutoutWithDisplayArea, false);
+        mHideDisplayCutoutController.updateStatus();
+        verify(mMockDisplayAreaOrganizer).disableHideDisplayCutout();
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
new file mode 100644
index 0000000..2e4fd6a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.hidedisplaycutout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.window.DisplayAreaAppearedInfo;
+import android.window.DisplayAreaInfo;
+import android.window.DisplayAreaOrganizer;
+import android.window.IWindowContainerToken;
+import android.window.WindowContainerToken;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.wm.shell.common.DisplayController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class HideDisplayCutoutOrganizerTest {
+    private TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private DisplayController mMockDisplayController;
+    private HideDisplayCutoutOrganizer mOrganizer;
+
+    private DisplayAreaInfo mDisplayAreaInfo;
+    private SurfaceControl mLeash;
+
+    @Mock
+    private Display mDisplay;
+    @Mock
+    private IWindowContainerToken mMockRealToken;
+    private WindowContainerToken mToken;
+
+    private final Rect mFakeDefaultBounds = new Rect(0, 0, 100, 200);
+    private final Insets mFakeDefaultCutoutInsets = Insets.of(0, 10, 0, 0);
+    private final int mFakeStatusBarHeightPortrait = 15;
+    private final int mFakeStatusBarHeightLandscape = 10;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
+
+        HideDisplayCutoutOrganizer organizer = new HideDisplayCutoutOrganizer(
+                mContext, mMockDisplayController);
+        mOrganizer = Mockito.spy(organizer);
+        doNothing().when(mOrganizer).unregisterOrganizer();
+        doNothing().when(mOrganizer).applyBoundsAndOffsets(any(), any(), any(), any());
+        doNothing().when(mOrganizer).applyTransaction(any(), any());
+
+        // It will be called when mDisplayAreaMap.containKey(token) is called.
+        Binder binder = new Binder();
+        doReturn(binder).when(mMockRealToken).asBinder();
+        mToken = new WindowContainerToken(mMockRealToken);
+        mLeash = new SurfaceControl();
+        mDisplayAreaInfo = new DisplayAreaInfo(
+                mToken, DEFAULT_DISPLAY, FEATURE_HIDE_DISPLAY_CUTOUT);
+        mDisplayAreaInfo.configuration.orientation = Configuration.ORIENTATION_PORTRAIT;
+        DisplayAreaAppearedInfo info = new DisplayAreaAppearedInfo(mDisplayAreaInfo, mLeash);
+        ArrayList<DisplayAreaAppearedInfo> infoList = new ArrayList<>();
+        infoList.add(info);
+        doReturn(infoList).when(mOrganizer).registerOrganizer(
+                DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+    }
+
+    @Test
+    public void testEnableHideDisplayCutout() {
+        mOrganizer.enableHideDisplayCutout();
+
+        verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+        verify(mOrganizer).addDisplayAreaInfoAndLeashToMap(mDisplayAreaInfo, mLeash);
+        verify(mOrganizer).updateBoundsAndOffsets(true);
+        assertThat(mOrganizer.mDisplayAreaMap.containsKey(mDisplayAreaInfo.token)).isTrue();
+        assertThat(mOrganizer.mDisplayAreaMap.containsValue(mLeash)).isTrue();
+    }
+
+    @Test
+    public void testOnDisplayAreaAppeared() {
+        mOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
+
+        assertThat(mOrganizer.mDisplayAreaMap.containsKey(mToken)).isTrue();
+        assertThat(mOrganizer.mDisplayAreaMap.containsValue(mLeash)).isTrue();
+    }
+
+    @Test
+    public void testOnDisplayAreaVanished() {
+        mOrganizer.mDisplayAreaMap.put(mDisplayAreaInfo.token, mLeash);
+        mOrganizer.onDisplayAreaVanished(mDisplayAreaInfo);
+
+        assertThat(mOrganizer.mDisplayAreaMap.containsKey(mDisplayAreaInfo.token)).isFalse();
+    }
+
+    @Test
+    public void testToggleHideDisplayCutout_enable_rot0() {
+        doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
+        doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
+                .getDisplayCutoutInsetsOfNaturalOrientation();
+        mContext.getOrCreateTestableResources().addOverride(
+                R.dimen.status_bar_height_portrait, mFakeStatusBarHeightPortrait);
+        doReturn(Surface.ROTATION_0).when(mDisplay).getRotation();
+        mOrganizer.enableHideDisplayCutout();
+
+        verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+        verify(mOrganizer).addDisplayAreaInfoAndLeashToMap(mDisplayAreaInfo, mLeash);
+        verify(mOrganizer).updateBoundsAndOffsets(true);
+        assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(0, 15, 100, 200));
+        assertThat(mOrganizer.mOffsetX).isEqualTo(0);
+        assertThat(mOrganizer.mOffsetY).isEqualTo(15);
+        assertThat(mOrganizer.mRotation).isEqualTo(Surface.ROTATION_0);
+    }
+
+    @Test
+    public void testToggleHideDisplayCutout_enable_rot90() {
+        doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
+        doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
+                .getDisplayCutoutInsetsOfNaturalOrientation();
+        mContext.getOrCreateTestableResources().addOverride(
+                R.dimen.status_bar_height_landscape, mFakeStatusBarHeightLandscape);
+        doReturn(Surface.ROTATION_90).when(mDisplay).getRotation();
+        mOrganizer.enableHideDisplayCutout();
+
+        verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+        verify(mOrganizer).addDisplayAreaInfoAndLeashToMap(mDisplayAreaInfo, mLeash);
+        verify(mOrganizer).updateBoundsAndOffsets(true);
+        assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(10, 0, 200, 100));
+        assertThat(mOrganizer.mOffsetX).isEqualTo(10);
+        assertThat(mOrganizer.mOffsetY).isEqualTo(0);
+        assertThat(mOrganizer.mRotation).isEqualTo(Surface.ROTATION_90);
+    }
+
+    @Test
+    public void testToggleHideDisplayCutout_enable_rot270() {
+        doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
+        doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
+                .getDisplayCutoutInsetsOfNaturalOrientation();
+        mContext.getOrCreateTestableResources().addOverride(
+                R.dimen.status_bar_height_landscape, mFakeStatusBarHeightLandscape);
+        doReturn(Surface.ROTATION_270).when(mDisplay).getRotation();
+        mOrganizer.enableHideDisplayCutout();
+
+        verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
+        verify(mOrganizer).addDisplayAreaInfoAndLeashToMap(mDisplayAreaInfo, mLeash);
+        verify(mOrganizer).updateBoundsAndOffsets(true);
+        assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(0, 0, 190, 100));
+        assertThat(mOrganizer.mOffsetX).isEqualTo(0);
+        assertThat(mOrganizer.mOffsetY).isEqualTo(0);
+        assertThat(mOrganizer.mRotation).isEqualTo(Surface.ROTATION_270);
+    }
+
+    @Test
+    public void testToggleHideDisplayCutout_disable() {
+        doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
+        doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
+                .getDisplayCutoutInsetsOfNaturalOrientation();
+        mContext.getOrCreateTestableResources().addOverride(
+                R.dimen.status_bar_height_portrait, mFakeStatusBarHeightPortrait);
+        mOrganizer.enableHideDisplayCutout();
+
+        // disable hide display cutout
+        mOrganizer.disableHideDisplayCutout();
+        verify(mOrganizer).updateBoundsAndOffsets(false);
+        verify(mOrganizer).unregisterOrganizer();
+        assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(0, 0, 0, 0));
+        assertThat(mOrganizer.mOffsetX).isEqualTo(0);
+        assertThat(mOrganizer.mOffsetY).isEqualTo(0);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index f073ced..7e48edf 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -109,6 +109,7 @@
                     .setSplitScreen(mWMComponent.getSplitScreen())
                     .setOneHanded(mWMComponent.getOneHanded())
                     .setBubbles(mWMComponent.getBubbles())
+                    .setHideDisplayCutout(mWMComponent.getHideDisplayCutout())
                     .setShellDump(mWMComponent.getShellDump());
         } else {
             // TODO: Call on prepareSysUIComponentBuilder but not with real components.
@@ -116,6 +117,7 @@
                     .setSplitScreen(Optional.ofNullable(null))
                     .setOneHanded(Optional.ofNullable(null))
                     .setBubbles(Optional.ofNullable(null))
+                    .setHideDisplayCutout(Optional.ofNullable(null))
                     .setShellDump(Optional.ofNullable(null));
         }
         mSysUIComponent = builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 54aeab5..02c3c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -26,6 +26,7 @@
 import com.android.systemui.util.InjectionInflationController;
 import com.android.wm.shell.ShellDump;
 import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.splitscreen.SplitScreen;
@@ -66,6 +67,9 @@
         Builder setBubbles(Optional<Bubbles> b);
 
         @BindsInstance
+        Builder setHideDisplayCutout(Optional<HideDisplayCutout> h);
+
+        @BindsInstance
         Builder setShellDump(Optional<ShellDump> shellDump);
 
         SysUIComponent build();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 9154ddb..b3e62e52 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -20,6 +20,7 @@
 import com.android.wm.shell.ShellDump;
 import com.android.wm.shell.ShellInit;
 import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.splitscreen.SplitScreen;
@@ -71,4 +72,7 @@
 
     @WMSingleton
     Optional<Bubbles> getBubbles();
+
+    @WMSingleton
+    Optional<HideDisplayCutout> getHideDisplayCutout();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 7a24438..04f1f86 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.wmshell;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
@@ -29,21 +27,14 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
-import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.content.ComponentName;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Pair;
 import android.view.KeyEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -55,8 +46,6 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -64,6 +53,7 @@
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.tracing.nano.SystemUiTraceProto;
 import com.android.wm.shell.ShellDump;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.nano.WmShellTraceProto;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEvents;
@@ -105,6 +95,7 @@
     private final Optional<Pip> mPipOptional;
     private final Optional<SplitScreen> mSplitScreenOptional;
     private final Optional<OneHanded> mOneHandedOptional;
+    private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
     private final ProtoTracer mProtoTracer;
     private final Optional<ShellDump> mShellDump;
 
@@ -123,6 +114,7 @@
             Optional<Pip> pipOptional,
             Optional<SplitScreen> splitScreenOptional,
             Optional<OneHanded> oneHandedOptional,
+            Optional<HideDisplayCutout> hideDisplayCutoutOptional,
             ProtoTracer protoTracer,
             Optional<ShellDump> shellDump) {
         super(context);
@@ -135,6 +127,7 @@
         mPipOptional = pipOptional;
         mSplitScreenOptional = splitScreenOptional;
         mOneHandedOptional = oneHandedOptional;
+        mHideDisplayCutoutOptional = hideDisplayCutoutOptional;
         mProtoTracer = protoTracer;
         mProtoTracer.add(this);
         mShellDump = shellDump;
@@ -146,6 +139,7 @@
         mPipOptional.ifPresent(this::initPip);
         mSplitScreenOptional.ifPresent(this::initSplitScreen);
         mOneHandedOptional.ifPresent(this::initOneHanded);
+        mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout);
     }
 
     @VisibleForTesting
@@ -284,6 +278,16 @@
         });
     }
 
+    @VisibleForTesting
+    void initHideDisplayCutout(HideDisplayCutout hideDisplayCutout) {
+        mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() {
+            @Override
+            public void onConfigChanged(Configuration newConfig) {
+                hideDisplayCutout.onConfigurationChanged(newConfig);
+            }
+        });
+    }
+
     @Override
     public void writeToProto(SystemUiTraceProto proto) {
         if (proto.wmShell == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index a197789..8c2980f 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -28,7 +28,6 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.shared.system.InputConsumerController;
 import com.android.wm.shell.ShellDump;
 import com.android.wm.shell.ShellInit;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -46,6 +45,8 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
@@ -90,9 +91,10 @@
     static Optional<ShellDump> provideShellDump(ShellTaskOrganizer shellTaskOrganizer,
             Optional<SplitScreen> splitScreenOptional,
             Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional) {
+            Optional<OneHanded> oneHandedOptional,
+            Optional<HideDisplayCutout> hideDisplayCutout) {
         return Optional.of(new ShellDump(shellTaskOrganizer, splitScreenOptional, pipOptional,
-                oneHandedOptional));
+                oneHandedOptional, hideDisplayCutout));
     }
 
     @WMSingleton
@@ -215,4 +217,10 @@
         return new HandlerExecutor(handler);
     }
 
+    @WMSingleton
+    @Provides
+    static Optional<HideDisplayCutout> provideHideDisplayCutoutController(Context context,
+            DisplayController displayController) {
+        return Optional.ofNullable(HideDisplayCutoutController.create(context, displayController));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 6a303a94..34889ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -20,7 +20,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.pm.PackageManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -31,13 +30,11 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.wm.shell.ShellDump;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedGestureHandler;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -56,13 +53,11 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class WMShellTest extends SysuiTestCase {
-    InputConsumerController mInputConsumerController;
     WMShell mWMShell;
 
     @Mock CommandQueue mCommandQueue;
     @Mock ConfigurationController mConfigurationController;
     @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock InputConsumerController mMockInputConsumerController;
     @Mock NavigationModeController mNavigationModeController;
     @Mock ScreenLifecycle mScreenLifecycle;
     @Mock SysUiState mSysUiState;
@@ -70,6 +65,7 @@
     @Mock PipTouchHandler mPipTouchHandler;
     @Mock SplitScreen mSplitScreen;
     @Mock OneHanded mOneHanded;
+    @Mock HideDisplayCutout mHideDisplayCutout;
     @Mock ProtoTracer mProtoTracer;
     @Mock ShellDump mShellDump;
 
@@ -80,7 +76,8 @@
         mWMShell = new WMShell(mContext, mCommandQueue, mConfigurationController,
                 mKeyguardUpdateMonitor, mNavigationModeController,
                 mScreenLifecycle, mSysUiState, Optional.of(mPip), Optional.of(mSplitScreen),
-                Optional.of(mOneHanded), mProtoTracer, Optional.of(mShellDump));
+                Optional.of(mOneHanded), Optional.of(mHideDisplayCutout), mProtoTracer,
+                Optional.of(mShellDump));
 
         when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler);
     }
@@ -113,4 +110,12 @@
                 OneHandedGestureHandler.OneHandedGestureEventCallback.class));
         verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
     }
+
+    @Test
+    public void initHideDisplayCutout_registersCallbacks() {
+        mWMShell.initHideDisplayCutout(mHideDisplayCutout);
+
+        verify(mConfigurationController).addCallback(
+                any(ConfigurationController.ConfigurationListener.class));
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index be18d0a..76a5cda 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -22,8 +22,11 @@
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION;
+import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT;
 import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
 
@@ -99,6 +102,12 @@
                             // layer
                             .setNewDisplayAreaSupplier(DisplayArea.Dimmable::new)
                             .build())
+                    .addFeature(new Feature.Builder(wmService.mPolicy, "HideDisplayCutout",
+                            FEATURE_HIDE_DISPLAY_CUTOUT)
+                            .all()
+                            .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR,
+                                    TYPE_NOTIFICATION_SHADE)
+                            .build())
                     .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded",
                             FEATURE_ONE_HANDED)
                             .all()
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 79e4e30..e964aac 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2965,10 +2965,6 @@
 
         mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
 
-        final int fullscreenAppearance = updateLightStatusBarLw(0 /* appearance */,
-                mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
-        final int dockedAppearance = updateLightStatusBarLw(0 /* appearance */,
-                mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
         final boolean inSplitScreen =
                 mService.mRoot.getDefaultTaskDisplayArea().isSplitScreenModeActivated();
         if (inSplitScreen) {
@@ -2980,6 +2976,12 @@
         mService.getStackBounds(inSplitScreen ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                         : WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_UNDEFINED, mNonDockedStackBounds);
+        final int fullscreenAppearance = updateLightStatusBarLw(0 /* appearance */,
+                mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState,
+                mNonDockedStackBounds);
+        final int dockedAppearance = updateLightStatusBarLw(0 /* appearance */,
+                mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState,
+                mDockedStackBounds);
         final int disableFlags = win.getSystemUiVisibility() & StatusBarManager.DISABLE_MASK;
         final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
         final WindowState navColorWin = chooseNavigationColorWindowLw(
@@ -3045,10 +3047,14 @@
     }
 
     private int updateLightStatusBarLw(@Appearance int appearance, WindowState opaque,
-            WindowState opaqueOrDimming) {
+            WindowState opaqueOrDimming, Rect stackBounds) {
+        final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
+        final int statusBarHeight = mStatusBarHeightForRotation[displayRotation.getRotation()];
+        final boolean stackBoundsContainStatusBar =
+                stackBounds.isEmpty() ? false : stackBounds.top < statusBarHeight;
         final boolean onKeyguard = isKeyguardShowing() && !isKeyguardOccluded();
         final WindowState statusColorWin = onKeyguard ? mNotificationShade : opaqueOrDimming;
-        if (statusColorWin != null) {
+        if (stackBoundsContainStatusBar && statusColorWin != null) {
             if (statusColorWin == opaque || onKeyguard) {
                 // If the top fullscreen-or-dimming window is also the top fullscreen, respect
                 // its light flag.