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