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
This commit is contained in:
shawnlin
2020-09-21 14:10:19 +08:00
parent f24001ca2b
commit 8dd26b237d
18 changed files with 849 additions and 31 deletions

View File

@@ -30,6 +30,7 @@ android_test {
"mockito-target-extended-minus-junit4",
"truth-prebuilt",
"testables",
"platform-test-annotations",
],
libs: [

View File

@@ -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();
}
}

View File

@@ -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);
}
}