/* * Copyright (C) 2019 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 android.server.wm; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.server.wm.app.Components.LAUNCHING_ACTIVITY; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_90; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; import android.app.Activity; import android.content.ComponentName; import android.content.pm.PackageManager; import android.graphics.Insets; import android.os.Bundle; import android.platform.test.annotations.Presubmit; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager.LayoutParams; import androidx.test.rule.ActivityTestRule; import com.android.compatibility.common.util.PollingCheck; import org.hamcrest.CustomTypeSafeMatcher; import org.hamcrest.Matcher; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ErrorCollector; import java.util.function.Supplier; @Presubmit public class WindowInsetsPolicyTest extends ActivityManagerTestBase { private static final String TAG = WindowInsetsPolicyTest.class.getSimpleName(); private ComponentName mTestActivityComponentName; @Rule public final ErrorCollector mErrorCollector = new ErrorCollector(); @Rule public final ActivityTestRule mTestActivity = new ActivityTestRule<>(TestActivity.class, false /* initialTouchMode */, false /* launchActivity */); @Rule public final ActivityTestRule mFullscreenTestActivity = new ActivityTestRule<>(FullscreenTestActivity.class, false /* initialTouchMode */, false /* launchActivity */); @Rule public final ActivityTestRule mFullscreenWmFlagsTestActivity = new ActivityTestRule<>(FullscreenWmFlagsTestActivity.class, false /* initialTouchMode */, false /* launchActivity */); @Rule public final ActivityTestRule mImmersiveTestActivity = new ActivityTestRule<>(ImmersiveFullscreenTestActivity.class, false /* initialTouchMode */, false /* launchActivity */); @Rule public final ActivityTestRule mNaturalOrientationTestActivity = new ActivityTestRule<>(NaturalOrientationTestActivity.class, false /* initialTouchMode */, false /* launchActivity */); @Before @Override public void setUp() throws Exception { super.setUp(); mTestActivityComponentName = new ComponentName(mContext, TestActivity.class); } @Test public void testWindowInsets_dispatched() { final TestActivity activity = launchAndWait(mTestActivity); WindowInsets insets = getOnMainSync(activity::getDispatchedInsets); Assert.assertThat("test setup failed, no insets dispatched", insets, notNullValue()); commonAsserts(insets); } @Test public void testWindowInsets_root() { final TestActivity activity = launchAndWait(mTestActivity); WindowInsets insets = getOnMainSync(activity::getRootInsets); Assert.assertThat("test setup failed, no insets at root", insets, notNullValue()); commonAsserts(insets); } /** * Tests whether an activity in split screen gets the top insets force consumed if * {@link View#SYSTEM_UI_FLAG_FULLSCREEN} is set, and doesn't otherwise. */ @Test public void testForcedConsumedTopInsets() throws Exception { assumeTrue("Skipping test: no split multi-window support", supportsSplitScreenMultiWindow()); launchAndWait(mNaturalOrientationTestActivity); mWmState.computeState(new ComponentName[] {}); final boolean naturalOrientationPortrait = mWmState.getDisplay(DEFAULT_DISPLAY) .mFullConfiguration.orientation == ORIENTATION_PORTRAIT; final RotationSession rotationSession = createManagedRotationSession(); rotationSession.set(naturalOrientationPortrait ? ROTATION_90 : ROTATION_0); final TestActivity activity = launchAndWait(mTestActivity); mWmState.waitForValidState(mTestActivityComponentName); final int taskId = mWmState.getTaskByActivity(mTestActivityComponentName).mTaskId; launchActivityInPrimarySplit(LAUNCHING_ACTIVITY); mTaskOrganizer.putTaskInSplitSecondary(taskId); mWmState.waitForValidState(mTestActivityComponentName); // Ensure that top insets are not consumed for LAYOUT_FULLSCREEN WindowInsets insets = getOnMainSync(activity::getDispatchedInsets); final WindowInsets rootInsets = getOnMainSync(activity::getRootInsets); assertEquals("top inset must be dispatched in split screen", rootInsets.getSystemWindowInsetTop(), insets.getSystemWindowInsetTop()); // Ensure that top insets are fully consumed for FULLSCREEN final TestActivity fullscreenActivity = launchAndWait(mFullscreenTestActivity); insets = getOnMainSync(fullscreenActivity::getDispatchedInsets); assertEquals("top insets must be consumed if FULLSCREEN is set", 0, insets.getSystemWindowInsetTop()); // Ensure that top insets are fully consumed for FULLSCREEN when setting it over wm // layout params final TestActivity fullscreenWmFlagsActivity = launchAndWait(mFullscreenWmFlagsTestActivity); insets = getOnMainSync(fullscreenWmFlagsActivity::getDispatchedInsets); assertEquals("top insets must be consumed if FULLSCREEN is set", 0, insets.getSystemWindowInsetTop()); } @Test public void testNonAutomotiveFullScreenNotBlockedBySystemComponents() { assumeFalse("Skipping test: Automotive is allowed to partially block fullscreen " + "applications with system bars.", isAutomotive()); final TestActivity fullscreenActivity = launchAndWait(mFullscreenTestActivity); View decorView = fullscreenActivity.getDecorView(); View contentView = decorView.findViewById(android.R.id.content); boolean hasFullWidth = decorView.getMeasuredWidth() == contentView.getMeasuredWidth(); boolean hasFullHeight = decorView.getMeasuredHeight() == contentView.getMeasuredHeight(); assertTrue(hasFullWidth && hasFullHeight); } @Test public void testImmersiveFullscreenHidesSystemBars() throws Throwable { // Run the test twice, because the issue that shows system bars even in the immersive mode, // happens at the 2nd try. for (int i = 1; i <= 2; ++i) { Log.d(TAG, "testImmersiveFullscreenHidesSystemBars: try" + i); TestActivity immersiveActivity = launchAndWait(mImmersiveTestActivity); WindowInsets insets = getOnMainSync(immersiveActivity::getDispatchedInsets); assertFalse(insets.isVisible(WindowInsets.Type.statusBars())); assertFalse(insets.isVisible(WindowInsets.Type.navigationBars())); WindowInsets rootInsets = getOnMainSync(immersiveActivity::getRootInsets); assertFalse(rootInsets.isVisible(WindowInsets.Type.statusBars())); assertFalse(rootInsets.isVisible(WindowInsets.Type.navigationBars())); View statusBarBgView = getOnMainSync(immersiveActivity::getStatusBarBackgroundView); // The status bar background view can be non-existent or invisible. assertTrue(statusBarBgView == null || statusBarBgView.getVisibility() == android.view.View.INVISIBLE); View navigationBarBgView = getOnMainSync( immersiveActivity::getNavigationBarBackgroundView); // The navigation bar background view can be non-existent or invisible. assertTrue(navigationBarBgView == null || navigationBarBgView.getVisibility() == android.view.View.INVISIBLE); } } private void commonAsserts(WindowInsets insets) { assertForAllInsets("must be non-negative", insets, insetsGreaterThanOrEqualTo(Insets.NONE)); assertThat("system gesture insets must include mandatory system gesture insets", insets.getMandatorySystemGestureInsets(), insetsLessThanOrEqualTo(insets.getSystemGestureInsets())); Insets stableAndSystem = Insets.min(insets.getSystemWindowInsets(), insets.getStableInsets()); assertThat("mandatory system gesture insets must include intersection between " + "stable and system window insets", stableAndSystem, insetsLessThanOrEqualTo(insets.getMandatorySystemGestureInsets())); assertThat("tappable insets must be at most system window insets", insets.getTappableElementInsets(), insetsLessThanOrEqualTo(insets.getSystemWindowInsets())); } private void assertForAllInsets(String reason, WindowInsets actual, Matcher matcher) { assertThat("getSystemWindowInsets" + ": " + reason, actual.getSystemWindowInsets(), matcher); assertThat("getStableInsets" + ": " + reason, actual.getStableInsets(), matcher); assertThat("getSystemGestureInsets" + ": " + reason, actual.getSystemGestureInsets(), matcher); assertThat("getMandatorySystemGestureInsets" + ": " + reason, actual.getMandatorySystemGestureInsets(), matcher); assertThat("getTappableElementInsets" + ": " + reason, actual.getTappableElementInsets(), matcher); } private void assertThat(String reason, T actual, Matcher matcher) { mErrorCollector.checkThat(reason, actual, matcher); } private R getOnMainSync(Supplier f) { final Object[] result = new Object[1]; runOnMainSync(() -> result[0] = f.get()); //noinspection unchecked return (R) result[0]; } private void runOnMainSync(Runnable runnable) { getInstrumentation().runOnMainSync(runnable); } private T launchAndWait(ActivityTestRule rule) { final T activity = rule.launchActivity(null); PollingCheck.waitFor(activity::hasWindowFocus); return activity; } private boolean isAutomotive() { return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } private static Matcher insetsLessThanOrEqualTo(Insets max) { return new CustomTypeSafeMatcher("must be smaller on each side than " + max) { @Override protected boolean matchesSafely(Insets actual) { return actual.left <= max.left && actual.top <= max.top && actual.right <= max.right && actual.bottom <= max.bottom; } }; } private static Matcher insetsGreaterThanOrEqualTo(Insets min) { return new CustomTypeSafeMatcher("must be greater on each side than " + min) { @Override protected boolean matchesSafely(Insets actual) { return actual.left >= min.left && actual.top >= min.top && actual.right >= min.right && actual.bottom >= min.bottom; } }; } public static class TestActivity extends Activity { private WindowInsets mDispatchedInsets; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().requestFeature(Window.FEATURE_NO_TITLE); View view = new View(this); view.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); view.setOnApplyWindowInsetsListener((v, insets) -> mDispatchedInsets = insets); setContentView(view); } View getDecorView() { return getWindow().getDecorView(); } View getStatusBarBackgroundView() { return getWindow().getStatusBarBackgroundView(); } View getNavigationBarBackgroundView() { return getWindow().getNavigationBarBackgroundView(); } WindowInsets getRootInsets() { return getWindow().getDecorView().getRootWindowInsets(); } WindowInsets getDispatchedInsets() { return mDispatchedInsets; } } public static class FullscreenTestActivity extends TestActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getDecorView().setSystemUiVisibility( getDecorView().getSystemUiVisibility() | View.SYSTEM_UI_FLAG_FULLSCREEN); } } public static class FullscreenWmFlagsTestActivity extends TestActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().addFlags(LayoutParams.FLAG_FULLSCREEN); } } public static class ImmersiveFullscreenTestActivity extends TestActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // See https://developer.android.com/training/system-ui/immersive#EnableFullscreen getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY // Set the content to appear under the system bars so that the // content doesn't resize when the system bars hide and show. | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // Hide the nav bar and status bar | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN); } } public static class NaturalOrientationTestActivity extends TestActivity { } }