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:
@@ -30,6 +30,7 @@ android_test {
|
||||
"mockito-target-extended-minus-junit4",
|
||||
"truth-prebuilt",
|
||||
"testables",
|
||||
"platform-test-annotations",
|
||||
],
|
||||
|
||||
libs: [
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user