• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 
17 package android.server.wm;
18 
19 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
20 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
21 import static android.view.Surface.ROTATION_0;
22 import static android.view.Surface.ROTATION_180;
23 import static android.view.Surface.ROTATION_90;
24 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
25 
26 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
27 
28 import static org.hamcrest.Matchers.notNullValue;
29 import static org.junit.Assert.assertEquals;
30 import static org.junit.Assert.assertFalse;
31 import static org.junit.Assert.assertTrue;
32 import static org.junit.Assume.assumeFalse;
33 import static org.junit.Assume.assumeTrue;
34 
35 import android.app.Activity;
36 import android.content.ComponentName;
37 import android.content.pm.PackageManager;
38 import android.graphics.Insets;
39 import android.os.Bundle;
40 import android.platform.test.annotations.Presubmit;
41 import android.util.Log;
42 import android.view.View;
43 import android.view.ViewGroup;
44 import android.view.Window;
45 import android.view.WindowInsets;
46 import android.view.WindowManager.LayoutParams;
47 
48 import androidx.test.rule.ActivityTestRule;
49 
50 import com.android.compatibility.common.util.WindowUtil;
51 
52 import org.hamcrest.CustomTypeSafeMatcher;
53 import org.hamcrest.Matcher;
54 import org.junit.Assert;
55 import org.junit.Before;
56 import org.junit.ClassRule;
57 import org.junit.Rule;
58 import org.junit.Test;
59 import org.junit.rules.ErrorCollector;
60 
61 import java.util.function.Supplier;
62 
63 @Presubmit
64 @android.server.wm.annotation.Group2
65 public class WindowInsetsPolicyTest extends ActivityManagerTestBase {
66     private static final String TAG = WindowInsetsPolicyTest.class.getSimpleName();
67 
68     private ComponentName mTestActivityComponentName;
69 
70     @ClassRule
71     public static DisableImmersiveModeConfirmationRule mDisableImmersiveModeConfirmationRule =
72             new DisableImmersiveModeConfirmationRule();
73 
74     @Rule
75     public final ErrorCollector mErrorCollector = new ErrorCollector();
76 
77     @Rule
78     public final ActivityTestRule<TestActivity> mTestActivity =
79             new ActivityTestRule<>(TestActivity.class, false /* initialTouchMode */,
80                     false /* launchActivity */);
81 
82     @Rule
83     public final ActivityTestRule<FullscreenTestActivity> mFullscreenTestActivity =
84             new ActivityTestRule<>(FullscreenTestActivity.class, false /* initialTouchMode */,
85                     false /* launchActivity */);
86 
87     @Rule
88     public final ActivityTestRule<FullscreenWmFlagsTestActivity> mFullscreenWmFlagsTestActivity =
89             new ActivityTestRule<>(FullscreenWmFlagsTestActivity.class,
90                     false /* initialTouchMode */, false /* launchActivity */);
91 
92     @Rule
93     public final ActivityTestRule<ImmersiveFullscreenTestActivity> mImmersiveTestActivity =
94             new ActivityTestRule<>(ImmersiveFullscreenTestActivity.class,
95                     false /* initialTouchMode */, false /* launchActivity */);
96 
97     @Before
98     @Override
setUp()99     public void setUp() throws Exception {
100         super.setUp();
101         mTestActivityComponentName = new ComponentName(mContext, TestActivity.class);
102     }
103 
104     @Test
testWindowInsets_dispatched()105     public void testWindowInsets_dispatched() {
106         final TestActivity activity = launchAndWait(mTestActivity);
107 
108         WindowInsets insets = getOnMainSync(activity::getDispatchedInsets);
109         Assert.assertThat("test setup failed, no insets dispatched", insets, notNullValue());
110 
111         commonAsserts(insets);
112     }
113 
114     @Test
testWindowInsets_root()115     public void testWindowInsets_root() {
116         final TestActivity activity = launchAndWait(mTestActivity);
117 
118         WindowInsets insets = getOnMainSync(activity::getRootInsets);
119         Assert.assertThat("test setup failed, no insets at root", insets, notNullValue());
120 
121         commonAsserts(insets);
122     }
123 
124     /**
125      * Tests whether an activity in split screen gets the top insets force consumed if
126      * {@link View#SYSTEM_UI_FLAG_FULLSCREEN} is set, and doesn't otherwise.
127      */
128     @Test
testForcedConsumedTopInsets()129     public void testForcedConsumedTopInsets() throws Exception {
130         assumeTrue("Skipping test: no split multi-window support",
131                 supportsSplitScreenMultiWindow());
132 
133         final TestActivity activity = launchAndWait(mTestActivity);
134         final int rotation = activity.getDisplay().getRotation();
135         final boolean isPortrait = activity.getResources().getConfiguration()
136                 .orientation == ORIENTATION_PORTRAIT;
137         final RotationSession rotationSession = createManagedRotationSession();
138         if (isPortrait) {
139             // Rotate to landscape.
140             rotationSession.set(rotation == ROTATION_0 || rotation == ROTATION_180
141                     ? ROTATION_90 : ROTATION_0);
142         } else {
143             // Keep in landscape.
144             rotationSession.set(rotation);
145         }
146 
147         mWmState.waitForValidState(mTestActivityComponentName);
148         final int taskId = mWmState.getTaskByActivity(mTestActivityComponentName).mTaskId;
149         launchActivityInPrimarySplit(LAUNCHING_ACTIVITY);
150         mTaskOrganizer.putTaskInSplitSecondary(taskId);
151         mWmState.waitForValidState(mTestActivityComponentName);
152 
153         // Ensure that top insets are not consumed for LAYOUT_FULLSCREEN
154         WindowInsets insets = getOnMainSync(activity::getDispatchedInsets);
155         final WindowInsets rootInsets = getOnMainSync(activity::getRootInsets);
156         assertEquals("top inset must be dispatched in split screen",
157                 rootInsets.getSystemWindowInsetTop(), insets.getSystemWindowInsetTop());
158 
159         // Ensure that top insets are fully consumed for FULLSCREEN
160         final TestActivity fullscreenActivity = launchAndWait(mFullscreenTestActivity);
161         insets = getOnMainSync(fullscreenActivity::getDispatchedInsets);
162         assertEquals("top insets must be consumed if FULLSCREEN is set",
163                 0, insets.getSystemWindowInsetTop());
164 
165         // Ensure that top insets are fully consumed for FULLSCREEN when setting it over wm
166         // layout params
167         final TestActivity fullscreenWmFlagsActivity =
168                 launchAndWait(mFullscreenWmFlagsTestActivity);
169         insets = getOnMainSync(fullscreenWmFlagsActivity::getDispatchedInsets);
170         assertEquals("top insets must be consumed if FULLSCREEN is set",
171                 0, insets.getSystemWindowInsetTop());
172     }
173 
174     @Test
testNonAutomotiveFullScreenNotBlockedBySystemComponents()175     public void testNonAutomotiveFullScreenNotBlockedBySystemComponents() {
176         assumeFalse("Skipping test: Automotive is allowed to partially block fullscreen "
177                         + "applications with system bars.", isAutomotive());
178 
179         final TestActivity fullscreenActivity = launchAndWait(mFullscreenTestActivity);
180         View decorView = fullscreenActivity.getDecorView();
181         View contentView = decorView.findViewById(android.R.id.content);
182         boolean hasFullWidth = decorView.getMeasuredWidth() == contentView.getMeasuredWidth();
183         boolean hasFullHeight = decorView.getMeasuredHeight() == contentView.getMeasuredHeight();
184 
185         assertTrue(hasFullWidth && hasFullHeight);
186     }
187 
188     @Test
testImmersiveFullscreenHidesSystemBars()189     public void testImmersiveFullscreenHidesSystemBars() throws Throwable {
190         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
191 
192         // Run the test twice, because the issue that shows system bars even in the immersive mode,
193         // happens at the 2nd try.
194         for (int i = 1; i <= 2; ++i) {
195             Log.d(TAG, "testImmersiveFullscreenHidesSystemBars: try" + i);
196 
197             TestActivity immersiveActivity = launchAndWait(mImmersiveTestActivity);
198             WindowInsets insets = getOnMainSync(immersiveActivity::getDispatchedInsets);
199 
200             assertFalse(insets.isVisible(WindowInsets.Type.statusBars()));
201             assertFalse(insets.isVisible(WindowInsets.Type.navigationBars()));
202 
203             WindowInsets rootInsets = getOnMainSync(immersiveActivity::getRootInsets);
204             assertFalse(rootInsets.isVisible(WindowInsets.Type.statusBars()));
205             assertFalse(rootInsets.isVisible(WindowInsets.Type.navigationBars()));
206 
207             View statusBarBgView = getOnMainSync(immersiveActivity::getStatusBarBackgroundView);
208             // The status bar background view can be non-existent or invisible.
209             assertTrue(statusBarBgView == null
210                     || statusBarBgView.getVisibility() == android.view.View.INVISIBLE);
211 
212             View navigationBarBgView = getOnMainSync(
213                     immersiveActivity::getNavigationBarBackgroundView);
214             // The navigation bar background view can be non-existent or invisible.
215             assertTrue(navigationBarBgView == null
216                     || navigationBarBgView.getVisibility() == android.view.View.INVISIBLE);
217         }
218     }
219 
commonAsserts(WindowInsets insets)220     private void commonAsserts(WindowInsets insets) {
221         assertForAllInsets("must be non-negative", insets, insetsGreaterThanOrEqualTo(Insets.NONE));
222 
223         assertThat("system gesture insets must include mandatory system gesture insets",
224                 insets.getMandatorySystemGestureInsets(),
225                 insetsLessThanOrEqualTo(insets.getSystemGestureInsets()));
226 
227         Insets stableAndSystem = Insets.min(insets.getSystemWindowInsets(),
228                 insets.getStableInsets());
229         assertThat("mandatory system gesture insets must include intersection between "
230                         + "stable and system window insets",
231                 stableAndSystem,
232                 insetsLessThanOrEqualTo(insets.getMandatorySystemGestureInsets()));
233 
234         assertThat("tappable insets must be at most system window insets",
235                 insets.getTappableElementInsets(),
236                 insetsLessThanOrEqualTo(insets.getSystemWindowInsets()));
237     }
238 
assertForAllInsets(String reason, WindowInsets actual, Matcher<? super Insets> matcher)239     private void assertForAllInsets(String reason, WindowInsets actual,
240             Matcher<? super Insets> matcher) {
241         assertThat("getSystemWindowInsets" + ": " + reason,
242                 actual.getSystemWindowInsets(), matcher);
243         assertThat("getStableInsets" + ": " + reason,
244                 actual.getStableInsets(), matcher);
245         assertThat("getSystemGestureInsets" + ": " + reason,
246                 actual.getSystemGestureInsets(), matcher);
247         assertThat("getMandatorySystemGestureInsets" + ": " + reason,
248                 actual.getMandatorySystemGestureInsets(), matcher);
249         assertThat("getTappableElementInsets" + ": " + reason,
250                 actual.getTappableElementInsets(), matcher);
251     }
252 
assertThat(String reason, T actual, Matcher<? super T> matcher)253     private <T> void assertThat(String reason, T actual, Matcher<? super T> matcher) {
254         mErrorCollector.checkThat(reason, actual, matcher);
255     }
256 
getOnMainSync(Supplier<R> f)257     private <R> R getOnMainSync(Supplier<R> f) {
258         final Object[] result = new Object[1];
259         runOnMainSync(() -> result[0] = f.get());
260         //noinspection unchecked
261         return (R) result[0];
262     }
263 
runOnMainSync(Runnable runnable)264     private void runOnMainSync(Runnable runnable) {
265         getInstrumentation().runOnMainSync(runnable);
266     }
267 
launchAndWait(ActivityTestRule<T> rule)268     private <T extends Activity> T launchAndWait(ActivityTestRule<T> rule) {
269         final T activity = rule.launchActivity(null);
270         WindowUtil.waitForFocus(activity);
271         return activity;
272     }
273 
isAutomotive()274     private boolean isAutomotive() {
275         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
276     }
277 
insetsLessThanOrEqualTo(Insets max)278     private static Matcher<Insets> insetsLessThanOrEqualTo(Insets max) {
279         return new CustomTypeSafeMatcher<Insets>("must be smaller on each side than " + max) {
280             @Override
281             protected boolean matchesSafely(Insets actual) {
282                 return actual.left <= max.left && actual.top <= max.top
283                         && actual.right <= max.right && actual.bottom <= max.bottom;
284             }
285         };
286     }
287 
288     private static Matcher<Insets> insetsGreaterThanOrEqualTo(Insets min) {
289         return new CustomTypeSafeMatcher<Insets>("must be greater on each side than " + min) {
290             @Override
291             protected boolean matchesSafely(Insets actual) {
292                 return actual.left >= min.left && actual.top >= min.top
293                         && actual.right >= min.right && actual.bottom >= min.bottom;
294             }
295         };
296     }
297 
298     public static class TestActivity extends Activity {
299 
300         private WindowInsets mDispatchedInsets;
301 
302         @Override
303         protected void onCreate(Bundle savedInstanceState) {
304             super.onCreate(savedInstanceState);
305             getWindow().requestFeature(Window.FEATURE_NO_TITLE);
306             View view = new View(this);
307             view.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
308             getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
309                     | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
310             view.setOnApplyWindowInsetsListener((v, insets) -> mDispatchedInsets = insets);
311             setContentView(view);
312         }
313 
314         View getDecorView() {
315             return getWindow().getDecorView();
316         }
317 
318         View getStatusBarBackgroundView() {
319             return getWindow().getStatusBarBackgroundView();
320         }
321 
322         View getNavigationBarBackgroundView() {
323             return getWindow().getNavigationBarBackgroundView();
324         }
325 
326         WindowInsets getRootInsets() {
327             return getWindow().getDecorView().getRootWindowInsets();
328         }
329 
330         WindowInsets getDispatchedInsets() {
331             return mDispatchedInsets;
332         }
333     }
334 
335     public static class FullscreenTestActivity extends TestActivity {
336 
337         @Override
338         protected void onCreate(Bundle savedInstanceState) {
339             super.onCreate(savedInstanceState);
340             getDecorView().setSystemUiVisibility(
341                     getDecorView().getSystemUiVisibility() | View.SYSTEM_UI_FLAG_FULLSCREEN);
342         }
343     }
344 
345     public static class FullscreenWmFlagsTestActivity extends TestActivity {
346 
347         @Override
348         protected void onCreate(Bundle savedInstanceState) {
349             super.onCreate(savedInstanceState);
350             getWindow().addFlags(LayoutParams.FLAG_FULLSCREEN);
351         }
352     }
353 
354     public static class ImmersiveFullscreenTestActivity extends TestActivity {
355 
356         @Override
357         protected void onCreate(Bundle savedInstanceState) {
358             super.onCreate(savedInstanceState);
359             // See https://developer.android.com/training/system-ui/immersive#EnableFullscreen
360             getDecorView().setSystemUiVisibility(
361                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
362                     // Set the content to appear under the system bars so that the
363                     // content doesn't resize when the system bars hide and show.
364                     | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
365                     | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
366                     | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
367                     // Hide the nav bar and status bar
368                     | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
369                     | View.SYSTEM_UI_FLAG_FULLSCREEN);
370         }
371     }
372 
373     public static class NaturalOrientationTestActivity extends TestActivity {
374     }
375 }
376