blob: 806cee588e7fa40c79c3473349e22d1d29339b83 [file] [log] [blame]
/*
* 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.provider.Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS;
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
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.provider.Settings;
import android.server.wm.settings.SettingsSession;
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.WindowUtil;
import org.hamcrest.CustomTypeSafeMatcher;
import org.hamcrest.Matcher;
import org.junit.After;
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;
private SettingsSession<String> mImmersiveModeConfirmationSetting;
@Rule
public final ErrorCollector mErrorCollector = new ErrorCollector();
@Rule
public final ActivityTestRule<TestActivity> mTestActivity =
new ActivityTestRule<>(TestActivity.class, false /* initialTouchMode */,
false /* launchActivity */);
@Rule
public final ActivityTestRule<FullscreenTestActivity> mFullscreenTestActivity =
new ActivityTestRule<>(FullscreenTestActivity.class, false /* initialTouchMode */,
false /* launchActivity */);
@Rule
public final ActivityTestRule<FullscreenWmFlagsTestActivity> mFullscreenWmFlagsTestActivity =
new ActivityTestRule<>(FullscreenWmFlagsTestActivity.class,
false /* initialTouchMode */, false /* launchActivity */);
@Rule
public final ActivityTestRule<ImmersiveFullscreenTestActivity> mImmersiveTestActivity =
new ActivityTestRule<>(ImmersiveFullscreenTestActivity.class,
false /* initialTouchMode */, false /* launchActivity */);
@Before
@Override
public void setUp() throws Exception {
super.setUp();
mTestActivityComponentName = new ComponentName(mContext, TestActivity.class);
mImmersiveModeConfirmationSetting = new SettingsSession<>(
Settings.Secure.getUriFor(IMMERSIVE_MODE_CONFIRMATIONS),
Settings.Secure::getString, Settings.Secure::putString);
mImmersiveModeConfirmationSetting.set("confirmed");
}
@After
public void tearDown() {
if (mImmersiveModeConfirmationSetting != null) {
mImmersiveModeConfirmationSetting.close();
}
}
@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());
final TestActivity activity = launchAndWait(mTestActivity);
final int rotation = activity.getDisplay().getRotation();
final boolean isPortrait = activity.getResources().getConfiguration()
.orientation == ORIENTATION_PORTRAIT;
final RotationSession rotationSession = createManagedRotationSession();
if (isPortrait) {
// Rotate to landscape.
rotationSession.set(rotation == ROTATION_0 || rotation == ROTATION_180
? ROTATION_90 : ROTATION_0);
} else {
// Keep in landscape.
rotationSession.set(rotation);
}
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<? super Insets> 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 <T> void assertThat(String reason, T actual, Matcher<? super T> matcher) {
mErrorCollector.checkThat(reason, actual, matcher);
}
private <R> R getOnMainSync(Supplier<R> 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 extends Activity> T launchAndWait(ActivityTestRule<T> rule) {
final T activity = rule.launchActivity(null);
WindowUtil.waitForFocus(activity);
return activity;
}
private boolean isAutomotive() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
}
private static Matcher<Insets> insetsLessThanOrEqualTo(Insets max) {
return new CustomTypeSafeMatcher<Insets>("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<Insets> insetsGreaterThanOrEqualTo(Insets min) {
return new CustomTypeSafeMatcher<Insets>("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 {
}
}