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.