diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java index bc3e35ccb8a9c..6cc3cd3987031 100644 --- a/core/java/android/window/DisplayAreaOrganizer.java +++ b/core/java/android/window/DisplayAreaOrganizer.java @@ -77,6 +77,12 @@ public class DisplayAreaOrganizer extends WindowOrganizer { */ 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 */ diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 433a46b73f4c8..3295df1122d01 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. --> 0.0 + + + false diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 6960fb34f58a1..fba431c66f531 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4095,4 +4095,6 @@ + + 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 4ba842239242d..bb9accd78bab1 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 @@ public class ShellDump { private final Optional mSplitScreenOptional; private final Optional mPipOptional; private final Optional mOneHandedOptional; + private final Optional mHideDisplayCutout; private final ShellTaskOrganizer mShellTaskOrganizer; public ShellDump(ShellTaskOrganizer shellTaskOrganizer, Optional splitScreenOptional, Optional pipOptional, - Optional oneHandedOptional) { + Optional oneHandedOptional, + Optional hideDisplayCutout) { mShellTaskOrganizer = shellTaskOrganizer; mSplitScreenOptional = splitScreenOptional; mPipOptional = pipOptional; mOneHandedOptional = oneHandedOptional; + mHideDisplayCutout = hideDisplayCutout; } public void dump(PrintWriter pw) { @@ -52,5 +54,6 @@ public class ShellDump { 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 0000000000000..38e0519b7a90c --- /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 0000000000000..e4e2546333950 --- /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 0000000000000..090d2270817bd --- /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 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 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 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 9940ea575873e..dca27328f1920 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -30,6 +30,7 @@ android_test { "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 0000000000000..595440f8d52db --- /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 0000000000000..2e4fd6a331ec5 --- /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 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 f073ced0bcee0..7e48edf3028ac 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -109,6 +109,7 @@ public class SystemUIFactory { .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 @@ public class SystemUIFactory { .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 54aeab50763ff..02c3c2f27e077 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.statusbar.policy.ConfigurationController; 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; @@ -65,6 +66,9 @@ public interface SysUIComponent { @BindsInstance Builder setBubbles(Optional b); + @BindsInstance + Builder setHideDisplayCutout(Optional h); + @BindsInstance Builder setShellDump(Optional shellDump); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index 9154ddba5d880..b3e62e52e31b9 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.systemui.wmshell.WMShellModule; 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 @@ public interface WMComponent { @WMSingleton Optional getBubbles(); + + @WMSingleton + Optional getHideDisplayCutout(); } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 7a244389a3bcf..04f1f86c3ac7a 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_O 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.dagger.SysUISingleton; 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.statusbar.policy.UserInfoController; 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 @@ public final class WMShell extends SystemUI private final Optional mPipOptional; private final Optional mSplitScreenOptional; private final Optional mOneHandedOptional; + private final Optional mHideDisplayCutoutOptional; private final ProtoTracer mProtoTracer; private final Optional mShellDump; @@ -123,6 +114,7 @@ public final class WMShell extends SystemUI Optional pipOptional, Optional splitScreenOptional, Optional oneHandedOptional, + Optional hideDisplayCutoutOptional, ProtoTracer protoTracer, Optional shellDump) { super(context); @@ -135,6 +127,7 @@ public final class WMShell extends SystemUI mPipOptional = pipOptional; mSplitScreenOptional = splitScreenOptional; mOneHandedOptional = oneHandedOptional; + mHideDisplayCutoutOptional = hideDisplayCutoutOptional; mProtoTracer = protoTracer; mProtoTracer.add(this); mShellDump = shellDump; @@ -146,6 +139,7 @@ public final class WMShell extends SystemUI mPipOptional.ifPresent(this::initPip); mSplitScreenOptional.ifPresent(this::initSplitScreen); mOneHandedOptional.ifPresent(this::initOneHanded); + mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout); } @VisibleForTesting @@ -284,6 +278,16 @@ public final class WMShell extends SystemUI }); } + @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 a197789a14f47..8c2980fddf960 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.logging.UiEventLogger; 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.SystemWindows; 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 @@ public abstract class WMShellBaseModule { static Optional provideShellDump(ShellTaskOrganizer shellTaskOrganizer, Optional splitScreenOptional, Optional pipOptional, - Optional oneHandedOptional) { + Optional oneHandedOptional, + Optional hideDisplayCutout) { return Optional.of(new ShellDump(shellTaskOrganizer, splitScreenOptional, pipOptional, - oneHandedOptional)); + oneHandedOptional, hideDisplayCutout)); } @WMSingleton @@ -215,4 +217,10 @@ public abstract class WMShellBaseModule { return new HandlerExecutor(handler); } + @WMSingleton + @Provides + static Optional 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 6a303a9419605..34889ffdf959b 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.ArgumentMatchers.any; 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.SysuiTestCase; 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 @@ import java.util.Optional; @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 @@ public class WMShellTest extends SysuiTestCase { @Mock PipTouchHandler mPipTouchHandler; @Mock SplitScreen mSplitScreen; @Mock OneHanded mOneHanded; + @Mock HideDisplayCutout mHideDisplayCutout; @Mock ProtoTracer mProtoTracer; @Mock ShellDump mShellDump; @@ -80,7 +76,8 @@ public class WMShellTest extends SysuiTestCase { 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 @@ public class WMShellTest extends SysuiTestCase { 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 be18d0a9a6df6..76a5cda9299ed 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_INPUT_METHOD_DIALOG; 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 @@ public abstract class DisplayAreaPolicy { // 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 9011ba0d06adb..61cc4306409d9 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2969,10 +2969,6 @@ public class DisplayPolicy { 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) { @@ -2984,6 +2980,12 @@ public class DisplayPolicy { 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( @@ -3049,10 +3051,14 @@ public class DisplayPolicy { } 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.