blob: e4f5d3d0b7b6385c6a5bd11e100b6a3a6054d801 [file] [log] [blame]
Adrian Roos26860fa2018-03-15 19:07:21 +01001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package android.server.wm;
18
Adrian Roos39bcb6e2020-04-14 11:53:48 +020019import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
20import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
21import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
22import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
23import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
Adrian Roos26860fa2018-03-15 19:07:21 +010024import static android.server.wm.DisplayCutoutTests.TestActivity.EXTRA_CUTOUT_MODE;
Adrian Roos39bcb6e2020-04-14 11:53:48 +020025import static android.server.wm.DisplayCutoutTests.TestActivity.EXTRA_ORIENTATION;
Adrian Roos26860fa2018-03-15 19:07:21 +010026import static android.server.wm.DisplayCutoutTests.TestDef.Which.DISPATCHED;
27import static android.server.wm.DisplayCutoutTests.TestDef.Which.ROOT;
28import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
shawnlinfe746d82020-01-17 17:55:58 +080029import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
Adrian Roos26860fa2018-03-15 19:07:21 +010030import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
31import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
32import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
33
Riddle Hsu420a61e2019-11-07 02:01:16 +080034import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
Brett Chabot2ca72e92019-02-15 11:21:51 -080035
Issei Suzuki770b5e62018-10-03 14:02:01 +090036import static org.hamcrest.Matchers.equalTo;
Tadashi G. Takaokaf280a9b2019-02-01 15:28:04 +090037import static org.hamcrest.Matchers.everyItem;
Adrian Roos26860fa2018-03-15 19:07:21 +010038import static org.hamcrest.Matchers.greaterThan;
39import static org.hamcrest.Matchers.greaterThanOrEqualTo;
40import static org.hamcrest.Matchers.hasItem;
41import static org.hamcrest.Matchers.hasSize;
42import static org.hamcrest.Matchers.is;
43import static org.hamcrest.Matchers.lessThanOrEqualTo;
44import static org.hamcrest.Matchers.not;
45import static org.hamcrest.Matchers.notNullValue;
46import static org.hamcrest.Matchers.nullValue;
Louis Chang948a5b42019-04-17 16:22:21 +080047import static org.junit.Assert.assertEquals;
48import static org.junit.Assert.assertTrue;
JianYang Liube952de2020-05-15 16:14:58 -070049import static org.junit.Assume.assumeTrue;
Adrian Roos26860fa2018-03-15 19:07:21 +010050
51import android.app.Activity;
52import android.content.Intent;
JianYang Liube952de2020-05-15 16:14:58 -070053import android.content.pm.PackageManager;
Louis Chang948a5b42019-04-17 16:22:21 +080054import android.graphics.Insets;
Adrian Roos26860fa2018-03-15 19:07:21 +010055import android.graphics.Point;
56import android.graphics.Rect;
57import android.os.Bundle;
58import android.platform.test.annotations.Presubmit;
Adrian Roos26860fa2018-03-15 19:07:21 +010059import android.view.DisplayCutout;
60import android.view.View;
61import android.view.ViewGroup;
Adrian Rooscf348882018-05-14 15:10:37 +020062import android.view.Window;
Adrian Roos26860fa2018-03-15 19:07:21 +010063import android.view.WindowInsets;
Adrian Roos39bcb6e2020-04-14 11:53:48 +020064import android.view.WindowInsets.Type;
Adrian Roos26860fa2018-03-15 19:07:21 +010065
Brett Chabot2ca72e92019-02-15 11:21:51 -080066import androidx.test.rule.ActivityTestRule;
67
Adrian Roos26860fa2018-03-15 19:07:21 +010068import com.android.compatibility.common.util.PollingCheck;
69
70import org.hamcrest.CustomTypeSafeMatcher;
71import org.hamcrest.FeatureMatcher;
72import org.hamcrest.Matcher;
73import org.junit.Assert;
74import org.junit.Rule;
75import org.junit.Test;
76import org.junit.rules.ErrorCollector;
Adrian Roos39bcb6e2020-04-14 11:53:48 +020077import org.junit.runner.RunWith;
78import org.junit.runners.Parameterized;
79import org.junit.runners.Parameterized.Parameter;
Adrian Roos26860fa2018-03-15 19:07:21 +010080
81import java.util.Arrays;
82import java.util.List;
83import java.util.function.Predicate;
84import java.util.function.Supplier;
85import java.util.stream.Collectors;
86
87/**
88 * Build/Install/Run:
89 * atest CtsWindowManagerDeviceTestCases:DisplayCutoutTests
90 */
Adrian Roos26860fa2018-03-15 19:07:21 +010091@Presubmit
Riddle Hsu79030522019-11-04 16:19:53 +080092@android.server.wm.annotation.Group3
Adrian Roos39bcb6e2020-04-14 11:53:48 +020093@RunWith(Parameterized.class)
Adrian Roos26860fa2018-03-15 19:07:21 +010094public class DisplayCutoutTests {
shawnlin6ffa1912020-02-22 15:13:32 +080095 static final String LEFT = "left";
96 static final String TOP = "top";
97 static final String RIGHT = "right";
98 static final String BOTTOM = "bottom";
Adrian Roos26860fa2018-03-15 19:07:21 +010099
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200100 @Parameterized.Parameters(name= "{1}({0})")
101 public static Object[][] data() {
102 return new Object[][]{
103 {SCREEN_ORIENTATION_PORTRAIT, "SCREEN_ORIENTATION_PORTRAIT"},
104 {SCREEN_ORIENTATION_LANDSCAPE, "SCREEN_ORIENTATION_LANDSCAPE"},
105 {SCREEN_ORIENTATION_REVERSE_LANDSCAPE, "SCREEN_ORIENTATION_REVERSE_LANDSCAPE"},
106 {SCREEN_ORIENTATION_REVERSE_PORTRAIT, "SCREEN_ORIENTATION_REVERSE_PORTRAIT"},
107 };
108 }
109
110 @Parameter(0)
111 public int orientation;
112
113 @Parameter(1)
114 public String orientationName;
115
Adrian Roos26860fa2018-03-15 19:07:21 +0100116 @Rule
117 public final ErrorCollector mErrorCollector = new ErrorCollector();
118
119 @Rule
120 public final ActivityTestRule<TestActivity> mDisplayCutoutActivity =
121 new ActivityTestRule<>(TestActivity.class, false /* initialTouchMode */,
122 false /* launchActivity */);
123
124 @Test
Louis Chang948a5b42019-04-17 16:22:21 +0800125 public void testConstructor() {
126 final Insets safeInsets = Insets.of(1, 2, 3, 4);
127 final Rect boundLeft = new Rect(5, 6, 7, 8);
128 final Rect boundTop = new Rect(9, 0, 10, 1);
129 final Rect boundRight = new Rect(2, 3, 4, 5);
130 final Rect boundBottom = new Rect(6, 7, 8, 9);
131
132 final DisplayCutout displayCutout =
133 new DisplayCutout(safeInsets, boundLeft, boundTop, boundRight, boundBottom);
134
135 assertEquals(safeInsets.left, displayCutout.getSafeInsetLeft());
136 assertEquals(safeInsets.top, displayCutout.getSafeInsetTop());
137 assertEquals(safeInsets.right, displayCutout.getSafeInsetRight());
138 assertEquals(safeInsets.bottom, displayCutout.getSafeInsetBottom());
139
140 assertTrue(boundLeft.equals(displayCutout.getBoundingRectLeft()));
141 assertTrue(boundTop.equals(displayCutout.getBoundingRectTop()));
142 assertTrue(boundRight.equals(displayCutout.getBoundingRectRight()));
143 assertTrue(boundBottom.equals(displayCutout.getBoundingRectBottom()));
shawnlina8dc0872020-01-17 15:11:19 +0800144
145 assertEquals(Insets.NONE, displayCutout.getWaterfallInsets());
146 }
147
148 @Test
149 public void testConstructor_waterfall() {
150 final Insets safeInsets = Insets.of(1, 2, 3, 4);
151 final Rect boundLeft = new Rect(5, 6, 7, 8);
152 final Rect boundTop = new Rect(9, 0, 10, 1);
153 final Rect boundRight = new Rect(2, 3, 4, 5);
154 final Rect boundBottom = new Rect(6, 7, 8, 9);
155 final Insets waterfallInsets = Insets.of(4, 8, 12, 16);
156
157 final DisplayCutout displayCutout =
158 new DisplayCutout(safeInsets, boundLeft, boundTop, boundRight, boundBottom,
159 waterfallInsets);
160
161 assertEquals(safeInsets.left, displayCutout.getSafeInsetLeft());
162 assertEquals(safeInsets.top, displayCutout.getSafeInsetTop());
163 assertEquals(safeInsets.right, displayCutout.getSafeInsetRight());
164 assertEquals(safeInsets.bottom, displayCutout.getSafeInsetBottom());
165
166 assertTrue(boundLeft.equals(displayCutout.getBoundingRectLeft()));
167 assertTrue(boundTop.equals(displayCutout.getBoundingRectTop()));
168 assertTrue(boundRight.equals(displayCutout.getBoundingRectRight()));
169 assertTrue(boundBottom.equals(displayCutout.getBoundingRectBottom()));
170
171 assertEquals(waterfallInsets, displayCutout.getWaterfallInsets());
Louis Chang948a5b42019-04-17 16:22:21 +0800172 }
173
174 @Test
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200175 public void testDisplayCutout_default() {
176 runTest(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
177 (activity, insets, displayCutout, which) -> {
Adrian Roos26860fa2018-03-15 19:07:21 +0100178 if (displayCutout == null) {
179 return;
180 }
181 if (which == ROOT) {
182 assertThat("cutout must be contained within system bars in default mode",
183 safeInsets(displayCutout), insetsLessThanOrEqualTo(stableInsets(insets)));
184 } else if (which == DISPATCHED) {
185 assertThat("must not dipatch to hierarchy in default mode",
186 displayCutout, nullValue());
187 }
188 });
189 }
190
191 @Test
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200192 public void testDisplayCutout_shortEdges() {
193 runTest(LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, (a, insets, cutout, which) -> {
Adrian Roos1fd97b112019-01-28 16:31:04 +0100194 if (which == ROOT) {
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200195 final Rect appBounds = getAppBounds(a);
196 final Insets displaySafeInsets = Insets.of(safeInsets(a.getDisplay().getCutout()));
197 final Insets expected;
198 if (appBounds.height() > appBounds.width()) {
199 // Portrait display
200 expected = Insets.of(0, displaySafeInsets.top, 0, displaySafeInsets.bottom);
201 } else if (appBounds.height() < appBounds.width()) {
202 // Landscape display
203 expected = Insets.of(displaySafeInsets.left, 0, displaySafeInsets.right, 0);
204 } else {
205 expected = Insets.NONE;
206 }
207 assertThat("cutout must provide the display's safe insets on short edges and zero"
208 + " on the long edges.",
209 Insets.of(safeInsets(cutout)),
210 equalTo(expected));
Adrian Roos1fd97b112019-01-28 16:31:04 +0100211 }
Adrian Roos26860fa2018-03-15 19:07:21 +0100212 });
213 }
214
215 @Test
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200216 public void testDisplayCutout_never() {
Adrian Roos26860fa2018-03-15 19:07:21 +0100217 runTest(LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER, (a, insets, displayCutout, which) -> {
218 assertThat("must not layout in cutout area in never mode", displayCutout, nullValue());
219 });
220 }
221
shawnlinfe746d82020-01-17 17:55:58 +0800222 @Test
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200223 public void testDisplayCutout_always() {
shawnlinfe746d82020-01-17 17:55:58 +0800224 runTest(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS, (a, insets, displayCutout, which) -> {
225 if (which == ROOT) {
226 assertThat("Display.getCutout() must equal view root cutout",
227 a.getDisplay().getCutout(), equalTo(displayCutout));
228 }
229 });
230 }
231
Adrian Roos26860fa2018-03-15 19:07:21 +0100232 private void runTest(int cutoutMode, TestDef test) {
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200233 runTest(cutoutMode, test, orientation);
234 }
235
236 private void runTest(int cutoutMode, TestDef test, int orientation) {
JianYang Liube952de2020-05-15 16:14:58 -0700237 assumeTrue("Skipping test: orientation not supported", supportsOrientation(orientation));
Adrian Roos26860fa2018-03-15 19:07:21 +0100238 final TestActivity activity = launchAndWait(mDisplayCutoutActivity,
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200239 cutoutMode, orientation);
Adrian Roos26860fa2018-03-15 19:07:21 +0100240
241 WindowInsets insets = getOnMainSync(activity::getRootInsets);
242 WindowInsets dispatchedInsets = getOnMainSync(activity::getDispatchedInsets);
243 Assert.assertThat("test setup failed, no insets at root", insets, notNullValue());
244 Assert.assertThat("test setup failed, no insets dispatched",
245 dispatchedInsets, notNullValue());
246
247 final DisplayCutout displayCutout = insets.getDisplayCutout();
248 final DisplayCutout dispatchedDisplayCutout = dispatchedInsets.getDisplayCutout();
Adrian Roos26860fa2018-03-15 19:07:21 +0100249 if (displayCutout != null) {
shawnlin6ffa1912020-02-22 15:13:32 +0800250 commonAsserts(activity, displayCutout);
shawnlinfe746d82020-01-17 17:55:58 +0800251 if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
252 shortEdgeAsserts(activity, insets, displayCutout);
253 }
shawnlin6ffa1912020-02-22 15:13:32 +0800254 assertCutoutsAreConsistentWithInsets(activity, displayCutout);
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200255 assertSafeInsetsAreConsistentWithDisplayCutoutInsets(insets);
Adrian Roos26860fa2018-03-15 19:07:21 +0100256 }
257 test.run(activity, insets, displayCutout, ROOT);
258
259 if (dispatchedDisplayCutout != null) {
shawnlin6ffa1912020-02-22 15:13:32 +0800260 commonAsserts(activity, dispatchedDisplayCutout);
shawnlinfe746d82020-01-17 17:55:58 +0800261 if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
262 shortEdgeAsserts(activity, insets, displayCutout);
263 }
shawnlin6ffa1912020-02-22 15:13:32 +0800264 assertCutoutsAreConsistentWithInsets(activity, dispatchedDisplayCutout);
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200265 if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
266 assertSafeInsetsAreConsistentWithDisplayCutoutInsets(dispatchedInsets);
267 }
Adrian Roos26860fa2018-03-15 19:07:21 +0100268 }
269 test.run(activity, dispatchedInsets, dispatchedDisplayCutout, DISPATCHED);
270 }
271
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200272 private void assertSafeInsetsAreConsistentWithDisplayCutoutInsets(WindowInsets insets) {
273 DisplayCutout cutout = insets.getDisplayCutout();
274 Insets safeInsets = Insets.of(safeInsets(cutout));
275 assertEquals("WindowInsets.getInsets(displayCutout()) must equal"
276 + " DisplayCutout.getSafeInsets()",
277 safeInsets, insets.getInsets(Type.displayCutout()));
278 assertEquals("WindowInsets.getInsetsIgnoringVisibility(displayCutout()) must equal"
279 + " DisplayCutout.getSafeInsets()",
280 safeInsets, insets.getInsetsIgnoringVisibility(Type.displayCutout()));
281 }
282
shawnlin6ffa1912020-02-22 15:13:32 +0800283 private void commonAsserts(TestActivity activity, DisplayCutout cutout) {
Adrian Roos26860fa2018-03-15 19:07:21 +0100284 assertSafeInsetsValid(cutout);
Adrian Roos26860fa2018-03-15 19:07:21 +0100285 assertCutoutsAreWithinSafeInsets(activity, cutout);
286 assertBoundsAreNonEmpty(cutout);
287 assertAtMostOneCutoutPerEdge(activity, cutout);
288 }
289
shawnlinfe746d82020-01-17 17:55:58 +0800290 private void shortEdgeAsserts(
291 TestActivity activity, WindowInsets insets, DisplayCutout cutout) {
292 assertOnlyShortEdgeHasInsets(activity, cutout);
shawnlin6ffa1912020-02-22 15:13:32 +0800293 assertOnlyShortEdgeHasBounds(activity, cutout);
294 assertThat("systemWindowInsets (also known as content insets) must be at least as "
295 + "large as cutout safe insets",
296 safeInsets(cutout), insetsLessThanOrEqualTo(systemWindowInsets(insets)));
shawnlinfe746d82020-01-17 17:55:58 +0800297 }
298
shawnlin6ffa1912020-02-22 15:13:32 +0800299 private void assertCutoutIsConsistentWithInset(String position, DisplayCutout cutout,
300 int safeInsetSize, Rect appBound) {
301 if (safeInsetSize > 0) {
302 assertThat("cutout must have a bound on the " + position,
303 hasBound(position, cutout, appBound), is(true));
Issei Suzuki770b5e62018-10-03 14:02:01 +0900304 } else {
shawnlin6ffa1912020-02-22 15:13:32 +0800305 assertThat("cutout must have no bound on the " + position,
306 hasBound(position, cutout, appBound), is(false));
Issei Suzuki770b5e62018-10-03 14:02:01 +0900307 }
308 }
309
shawnlin6ffa1912020-02-22 15:13:32 +0800310 public void assertCutoutsAreConsistentWithInsets(TestActivity activity, DisplayCutout cutout) {
311 final Rect appBounds = getAppBounds(activity);
312 assertCutoutIsConsistentWithInset(TOP, cutout, cutout.getSafeInsetTop(), appBounds);
313 assertCutoutIsConsistentWithInset(BOTTOM, cutout, cutout.getSafeInsetBottom(), appBounds);
314 assertCutoutIsConsistentWithInset(LEFT, cutout, cutout.getSafeInsetLeft(), appBounds);
315 assertCutoutIsConsistentWithInset(RIGHT, cutout, cutout.getSafeInsetRight(), appBounds);
Issei Suzuki770b5e62018-10-03 14:02:01 +0900316 }
317
Adrian Roos26860fa2018-03-15 19:07:21 +0100318 private void assertSafeInsetsValid(DisplayCutout displayCutout) {
319 //noinspection unchecked
320 assertThat("all safe insets must be non-negative", safeInsets(displayCutout),
321 insetValues(everyItem((Matcher)greaterThanOrEqualTo(0))));
322 assertThat("at least one safe inset must be positive,"
323 + " otherwise WindowInsets.getDisplayCutout()) must return null",
324 safeInsets(displayCutout), insetValues(hasItem(greaterThan(0))));
325 }
326
327 private void assertCutoutsAreWithinSafeInsets(TestActivity a, DisplayCutout cutout) {
328 final Rect safeRect = getSafeRect(a, cutout);
329
330 assertThat("safe insets must not cover the entire screen", safeRect.isEmpty(), is(false));
331 for (Rect boundingRect : cutout.getBoundingRects()) {
332 assertThat("boundingRects must not extend beyond safeInsets",
333 boundingRect, not(intersectsWith(safeRect)));
334 }
335 }
336
337 private void assertAtMostOneCutoutPerEdge(TestActivity a, DisplayCutout cutout) {
338 final Rect safeRect = getSafeRect(a, cutout);
339
340 assertThat("must not have more than one left cutout",
341 boundsWith(cutout, (r) -> r.right <= safeRect.left), hasSize(lessThanOrEqualTo(1)));
342 assertThat("must not have more than one top cutout",
343 boundsWith(cutout, (r) -> r.bottom <= safeRect.top), hasSize(lessThanOrEqualTo(1)));
344 assertThat("must not have more than one right cutout",
345 boundsWith(cutout, (r) -> r.left >= safeRect.right), hasSize(lessThanOrEqualTo(1)));
346 assertThat("must not have more than one bottom cutout",
347 boundsWith(cutout, (r) -> r.top >= safeRect.bottom), hasSize(lessThanOrEqualTo(1)));
348 }
349
350 private void assertBoundsAreNonEmpty(DisplayCutout cutout) {
351 for (Rect boundingRect : cutout.getBoundingRects()) {
352 assertThat("rect in boundingRects must not be empty",
353 boundingRect.isEmpty(), is(false));
354 }
355 }
356
357 private void assertOnlyShortEdgeHasInsets(TestActivity activity,
358 DisplayCutout displayCutout) {
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200359 final Rect appBounds = getAppBounds(activity);
360 if (appBounds.height() > appBounds.width()) {
Issei Suzuki770b5e62018-10-03 14:02:01 +0900361 // Portrait display
Adrian Roos26860fa2018-03-15 19:07:21 +0100362 assertThat("left edge has a cutout despite being long edge",
363 displayCutout.getSafeInsetLeft(), is(0));
364 assertThat("right edge has a cutout despite being long edge",
365 displayCutout.getSafeInsetRight(), is(0));
366 }
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200367 if (appBounds.height() < appBounds.width()) {
Issei Suzuki770b5e62018-10-03 14:02:01 +0900368 // Landscape display
Adrian Roos26860fa2018-03-15 19:07:21 +0100369 assertThat("top edge has a cutout despite being long edge",
370 displayCutout.getSafeInsetTop(), is(0));
371 assertThat("bottom edge has a cutout despite being long edge",
372 displayCutout.getSafeInsetBottom(), is(0));
373 }
374 }
375
shawnlin6ffa1912020-02-22 15:13:32 +0800376 private void assertOnlyShortEdgeHasBounds(TestActivity activity, DisplayCutout cutout) {
shawnlin6ffa1912020-02-22 15:13:32 +0800377 final Rect appBounds = getAppBounds(activity);
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200378 if (appBounds.height() > appBounds.width()) {
Issei Suzuki770b5e62018-10-03 14:02:01 +0900379 // Portrait display
380 assertThat("left edge has a cutout despite being long edge",
shawnlin6ffa1912020-02-22 15:13:32 +0800381 hasBound(LEFT, cutout, appBounds), is(false));
382
Issei Suzuki770b5e62018-10-03 14:02:01 +0900383 assertThat("right edge has a cutout despite being long edge",
shawnlin6ffa1912020-02-22 15:13:32 +0800384 hasBound(RIGHT, cutout, appBounds), is(false));
Issei Suzuki770b5e62018-10-03 14:02:01 +0900385 }
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200386 if (appBounds.height() < appBounds.width()) {
Issei Suzuki770b5e62018-10-03 14:02:01 +0900387 // Landscape display
388 assertThat("top edge has a cutout despite being long edge",
shawnlin6ffa1912020-02-22 15:13:32 +0800389 hasBound(TOP, cutout, appBounds), is(false));
390
Issei Suzuki770b5e62018-10-03 14:02:01 +0900391 assertThat("bottom edge has a cutout despite being long edge",
shawnlin6ffa1912020-02-22 15:13:32 +0800392 hasBound(BOTTOM, cutout, appBounds), is(false));
Issei Suzuki770b5e62018-10-03 14:02:01 +0900393 }
394 }
395
shawnlin6ffa1912020-02-22 15:13:32 +0800396 private boolean hasBound(String position, DisplayCutout cutout, Rect appBound) {
397 final Rect cutoutRect;
398 final int waterfallSize;
399 if (LEFT.equals(position)) {
400 cutoutRect = cutout.getBoundingRectLeft();
401 waterfallSize = cutout.getWaterfallInsets().left;
402 } else if (TOP.equals(position)) {
403 cutoutRect = cutout.getBoundingRectTop();
404 waterfallSize = cutout.getWaterfallInsets().top;
405 } else if (RIGHT.equals(position)) {
406 cutoutRect = cutout.getBoundingRectRight();
407 waterfallSize = cutout.getWaterfallInsets().right;
408 } else {
409 cutoutRect = cutout.getBoundingRectBottom();
410 waterfallSize = cutout.getWaterfallInsets().bottom;
411 }
412 return Rect.intersects(cutoutRect, appBound) || waterfallSize > 0;
413 }
414
Adrian Roos26860fa2018-03-15 19:07:21 +0100415 private List<Rect> boundsWith(DisplayCutout cutout, Predicate<Rect> predicate) {
416 return cutout.getBoundingRects().stream().filter(predicate).collect(Collectors.toList());
417 }
418
Adrian Roos26860fa2018-03-15 19:07:21 +0100419 private static Rect safeInsets(DisplayCutout displayCutout) {
shawnlin6ffa1912020-02-22 15:13:32 +0800420 if (displayCutout == null) {
421 return null;
422 }
Adrian Roos26860fa2018-03-15 19:07:21 +0100423 return new Rect(displayCutout.getSafeInsetLeft(), displayCutout.getSafeInsetTop(),
424 displayCutout.getSafeInsetRight(), displayCutout.getSafeInsetBottom());
425 }
426
427 private static Rect systemWindowInsets(WindowInsets insets) {
428 return new Rect(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
429 insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
430 }
431
432 private static Rect stableInsets(WindowInsets insets) {
433 return new Rect(insets.getStableInsetLeft(), insets.getStableInsetTop(),
434 insets.getStableInsetRight(), insets.getStableInsetBottom());
435 }
436
437 private Rect getSafeRect(TestActivity a, DisplayCutout cutout) {
438 final Rect safeRect = safeInsets(cutout);
439 safeRect.bottom = getOnMainSync(() -> a.getDecorView().getHeight()) - safeRect.bottom;
440 safeRect.right = getOnMainSync(() -> a.getDecorView().getWidth()) - safeRect.right;
441 return safeRect;
442 }
443
shawnlin6ffa1912020-02-22 15:13:32 +0800444 private Rect getAppBounds(TestActivity a) {
445 final Rect appBounds = new Rect();
446 runOnMainSync(() -> {
447 appBounds.right = a.getDecorView().getWidth();
448 appBounds.bottom = a.getDecorView().getHeight();
449 });
450 return appBounds;
451 }
452
Adrian Roos26860fa2018-03-15 19:07:21 +0100453 private static Matcher<Rect> insetsLessThanOrEqualTo(Rect max) {
454 return new CustomTypeSafeMatcher<Rect>("must be smaller on each side than " + max) {
455 @Override
456 protected boolean matchesSafely(Rect actual) {
457 return actual.left <= max.left && actual.top <= max.top
458 && actual.right <= max.right && actual.bottom <= max.bottom;
459 }
460 };
461 }
462
463 private static Matcher<Rect> intersectsWith(Rect safeRect) {
464 return new CustomTypeSafeMatcher<Rect>("intersects with " + safeRect) {
465 @Override
466 protected boolean matchesSafely(Rect item) {
467 return Rect.intersects(safeRect, item);
468 }
469 };
470 }
471
472 private static Matcher<Rect> insetValues(Matcher<Iterable<? super Integer>> valuesMatcher) {
473 return new FeatureMatcher<Rect, Iterable<Integer>>(valuesMatcher, "inset values",
474 "inset values") {
475 @Override
476 protected Iterable<Integer> featureValueOf(Rect actual) {
477 return Arrays.asList(actual.left, actual.top, actual.right, actual.bottom);
478 }
479 };
480 }
481
482 private <T> void assertThat(String reason, T actual, Matcher<? super T> matcher) {
483 mErrorCollector.checkThat(reason, actual, matcher);
484 }
485
486 private <R> R getOnMainSync(Supplier<R> f) {
487 final Object[] result = new Object[1];
488 runOnMainSync(() -> result[0] = f.get());
489 //noinspection unchecked
490 return (R) result[0];
491 }
492
493 private void runOnMainSync(Runnable runnable) {
Tadashi G. Takaokaf280a9b2019-02-01 15:28:04 +0900494 getInstrumentation().runOnMainSync(runnable);
Adrian Roos26860fa2018-03-15 19:07:21 +0100495 }
496
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200497 private <T extends TestActivity> T launchAndWait(ActivityTestRule<T> rule, int cutoutMode,
498 int orientation) {
Adrian Roos26860fa2018-03-15 19:07:21 +0100499 final T activity = rule.launchActivity(
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200500 new Intent().putExtra(EXTRA_CUTOUT_MODE, cutoutMode)
501 .putExtra(EXTRA_ORIENTATION, orientation));
Adrian Roos26860fa2018-03-15 19:07:21 +0100502 PollingCheck.waitFor(activity::hasWindowFocus);
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200503 PollingCheck.waitFor(() -> {
504 final Rect appBounds = getAppBounds(activity);
505 final Point displaySize = new Point();
506 activity.getDisplay().getRealSize(displaySize);
507 // During app launch into a different rotation, we have temporarily have the display
508 // in a different rotation than the app itself. Wait for this to settle.
509 return (appBounds.width() > appBounds.height()) == (displaySize.x > displaySize.y);
510 });
Adrian Roos26860fa2018-03-15 19:07:21 +0100511 return activity;
512 }
513
JianYang Liube952de2020-05-15 16:14:58 -0700514 private boolean supportsOrientation(int orientation) {
515 String systemFeature = "";
516 switch(orientation) {
517 case SCREEN_ORIENTATION_PORTRAIT:
518 case SCREEN_ORIENTATION_REVERSE_PORTRAIT:
519 systemFeature = PackageManager.FEATURE_SCREEN_PORTRAIT;
520 break;
521 case SCREEN_ORIENTATION_LANDSCAPE:
522 case SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
523 systemFeature = PackageManager.FEATURE_SCREEN_LANDSCAPE;
524 break;
525 default:
526 throw new UnsupportedOperationException("Orientation not supported");
527 }
528
529 return getInstrumentation().getTargetContext().getPackageManager()
530 .hasSystemFeature(systemFeature);
531 }
532
Adrian Roos26860fa2018-03-15 19:07:21 +0100533 public static class TestActivity extends Activity {
534
535 static final String EXTRA_CUTOUT_MODE = "extra.cutout_mode";
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200536 static final String EXTRA_ORIENTATION = "extra.orientation";
Adrian Roos26860fa2018-03-15 19:07:21 +0100537 private WindowInsets mDispatchedInsets;
538
539 @Override
540 protected void onCreate(Bundle savedInstanceState) {
541 super.onCreate(savedInstanceState);
Adrian Rooscf348882018-05-14 15:10:37 +0200542 getWindow().requestFeature(Window.FEATURE_NO_TITLE);
Adrian Roos26860fa2018-03-15 19:07:21 +0100543 if (getIntent() != null) {
544 getWindow().getAttributes().layoutInDisplayCutoutMode = getIntent().getIntExtra(
545 EXTRA_CUTOUT_MODE, LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT);
Adrian Roos39bcb6e2020-04-14 11:53:48 +0200546 setRequestedOrientation(getIntent().getIntExtra(
547 EXTRA_ORIENTATION, SCREEN_ORIENTATION_UNSPECIFIED));
Adrian Roos26860fa2018-03-15 19:07:21 +0100548 }
549 View view = new View(this);
550 view.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
Adrian Roos26860fa2018-03-15 19:07:21 +0100551 view.setOnApplyWindowInsetsListener((v, insets) -> mDispatchedInsets = insets);
552 setContentView(view);
553 }
554
Jeff Chang844bd812018-12-05 14:13:08 +0800555 @Override
556 public void onWindowFocusChanged(boolean hasFocus) {
557 if (hasFocus) {
558 getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
shawnlin648195c2020-10-14 15:06:42 +0800559 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
560 | View.SYSTEM_UI_FLAG_FULLSCREEN
561 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
Jeff Chang844bd812018-12-05 14:13:08 +0800562 }
563 }
564
Adrian Roos26860fa2018-03-15 19:07:21 +0100565 View getDecorView() {
566 return getWindow().getDecorView();
567 }
568
569 WindowInsets getRootInsets() {
570 return getWindow().getDecorView().getRootWindowInsets();
571 }
572
573 WindowInsets getDispatchedInsets() {
574 return mDispatchedInsets;
575 }
576 }
577
578 interface TestDef {
579 void run(TestActivity a, WindowInsets insets, DisplayCutout cutout, Which whichInsets);
580
581 enum Which {
582 DISPATCHED, ROOT
583 }
584 }
585}