1 /* 2 * Copyright (C) 2020 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.insets; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 20 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 21 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 22 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 23 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 24 25 import static androidx.test.InstrumentationRegistry.getInstrumentation; 26 27 import static org.junit.Assert.assertEquals; 28 29 import android.graphics.Insets; 30 import android.graphics.Rect; 31 import android.os.Bundle; 32 import android.platform.test.annotations.Presubmit; 33 import android.server.wm.WindowManagerTestBase; 34 import android.view.View; 35 import android.view.WindowInsets; 36 import android.view.WindowInsets.Side; 37 import android.view.WindowInsets.Type; 38 import android.view.WindowManager; 39 40 import androidx.annotation.Nullable; 41 42 import com.android.compatibility.common.util.PollingCheck; 43 44 import org.junit.Test; 45 46 /** 47 * Test whether WindowManager performs the correct layout while the app applies the fit-window- 48 * insets APIs. 49 * 50 * Build/Install/Run: 51 * atest CtsWindowManagerDeviceInsets:WindowInsetsLayoutTests 52 */ 53 @Presubmit 54 @android.server.wm.annotation.Group2 55 public class WindowInsetsLayoutTests extends WindowManagerTestBase { 56 57 private static final long TIMEOUT = 1000; // milliseconds 58 59 @Test testSetFitInsetsTypes()60 public void testSetFitInsetsTypes() { 61 // Start the Activity in fullscreen windowing mode for its bounds to match display bounds. 62 final TestActivity activity = 63 startActivityInWindowingMode(TestActivity.class, WINDOWING_MODE_FULLSCREEN); 64 65 // Make sure the main window has been laid out. 66 final View mainWindowRoot = activity.getWindow().getDecorView(); 67 PollingCheck.waitFor(TIMEOUT, () -> mainWindowRoot.getWidth() > 0); 68 69 getInstrumentation().runOnMainSync(() -> { 70 activity.assertMatchesWindowBounds(); 71 }); 72 73 testSetFitInsetsTypesInner(Type.statusBars(), activity); 74 testSetFitInsetsTypesInner(Type.navigationBars(), activity); 75 testSetFitInsetsTypesInner(Type.systemBars(), activity); 76 } 77 testSetFitInsetsTypesInner(int types, TestActivity activity)78 private void testSetFitInsetsTypesInner(int types, TestActivity activity) { 79 getInstrumentation().runOnMainSync(() -> { 80 activity.addOverlayWindow(types, Side.all(), false); 81 }); 82 83 // Make sure the overlay window has been laid out. 84 final View overlayWindowRoot = activity.getOverlayWindowRoot(); 85 PollingCheck.waitFor(TIMEOUT, () -> overlayWindowRoot.getWidth() > 0); 86 87 getInstrumentation().runOnMainSync(() -> { 88 final WindowInsets windowInsets = overlayWindowRoot.getRootWindowInsets(); 89 final Insets insets = windowInsets.getInsets(types); 90 assertEquals(Insets.NONE, insets); 91 activity.removeOverlayWindow(); 92 }); 93 } 94 95 @Test testSetFitInsetsSides()96 public void testSetFitInsetsSides() { 97 // Start the Activity in fullscreen windowing mode for its bounds to match display bounds. 98 final TestActivity activity = 99 startActivityInWindowingMode(TestActivity.class, WINDOWING_MODE_FULLSCREEN); 100 101 // Make sure the main window has been laid out. 102 final View mainWindowRoot = activity.getWindow().getDecorView(); 103 PollingCheck.waitFor(TIMEOUT, () -> mainWindowRoot.getWidth() > 0); 104 105 getInstrumentation().runOnMainSync(() -> { 106 activity.assertMatchesWindowBounds(); 107 }); 108 109 testSetFitInsetsSidesInner(Side.LEFT, activity); 110 testSetFitInsetsSidesInner(Side.TOP, activity); 111 testSetFitInsetsSidesInner(Side.RIGHT, activity); 112 testSetFitInsetsSidesInner(Side.BOTTOM, activity); 113 } 114 testSetFitInsetsSidesInner(int sides, TestActivity activity)115 private void testSetFitInsetsSidesInner(int sides, TestActivity activity) { 116 final int types = Type.systemBars(); 117 getInstrumentation().runOnMainSync(() -> { 118 activity.addOverlayWindow(types, sides, false); 119 }); 120 121 // Make sure the overlay window has been laid out. 122 final View overlayWindowRoot = activity.getOverlayWindowRoot(); 123 PollingCheck.waitFor(TIMEOUT, () -> overlayWindowRoot.getWidth() > 0); 124 125 getInstrumentation().runOnMainSync(() -> { 126 final WindowInsets windowInsets = overlayWindowRoot.getRootWindowInsets(); 127 final Insets insets = windowInsets.getInsets(types); 128 if ((sides & Side.LEFT) != 0) { 129 assertEquals(0, insets.left); 130 } 131 if ((sides & Side.TOP) != 0) { 132 assertEquals(0, insets.top); 133 } 134 if ((sides & Side.RIGHT) != 0) { 135 assertEquals(0, insets.right); 136 } 137 if ((sides & Side.BOTTOM) != 0) { 138 assertEquals(0, insets.bottom); 139 } 140 activity.removeOverlayWindow(); 141 }); 142 } 143 144 @Test testSetFitInsetsIgnoringVisibility()145 public void testSetFitInsetsIgnoringVisibility() { 146 // Start the Activity in fullscreen windowing mode for its bounds to match display bounds. 147 final TestActivity activity = 148 startActivityInWindowingMode(TestActivity.class, WINDOWING_MODE_FULLSCREEN); 149 150 // Make sure the main window has been laid out. 151 final View mainWindowRoot = activity.getWindow().getDecorView(); 152 PollingCheck.waitFor(TIMEOUT, () -> mainWindowRoot.getWidth() > 0); 153 154 final int types = Type.systemBars(); 155 final int sides = Side.all(); 156 final int[] locationAndSize1 = new int[4]; 157 final int[] locationAndSize2 = new int[4]; 158 159 getInstrumentation().runOnMainSync(() -> { 160 activity.assertMatchesWindowBounds(); 161 activity.addOverlayWindow(types, sides, false); 162 }); 163 164 // Make sure the 1st overlay window has been laid out. 165 final View overlayWindowRoot1 = activity.getOverlayWindowRoot(); 166 PollingCheck.waitFor(TIMEOUT, () -> overlayWindowRoot1.getWidth() > 0); 167 168 getInstrumentation().runOnMainSync(() -> { 169 overlayWindowRoot1.getLocationOnScreen(locationAndSize1); 170 locationAndSize1[2] = overlayWindowRoot1.getWidth(); 171 locationAndSize1[3] = overlayWindowRoot1.getHeight(); 172 activity.removeOverlayWindow(); 173 174 mainWindowRoot.getWindowInsetsController().hide(types); 175 176 activity.addOverlayWindow(types, sides, true); 177 }); 178 179 // Make sure the 2nd overlay window has been laid out. 180 final View overlayWindowRoot2 = activity.getOverlayWindowRoot(); 181 PollingCheck.waitFor(TIMEOUT, () -> overlayWindowRoot2.getWidth() > 0); 182 183 getInstrumentation().runOnMainSync(() -> { 184 overlayWindowRoot2.getLocationOnScreen(locationAndSize2); 185 locationAndSize2[2] = overlayWindowRoot2.getWidth(); 186 locationAndSize2[3] = overlayWindowRoot2.getHeight(); 187 activity.removeOverlayWindow(); 188 }); 189 190 for (int i = 0; i < 4; i++) { 191 assertEquals(locationAndSize1[i], locationAndSize2[i]); 192 } 193 } 194 195 public static class TestActivity extends FocusableActivity { 196 197 private View mOverlayWindowRoot; 198 199 @Override onCreate(@ullable Bundle savedInstanceState)200 protected void onCreate(@Nullable Bundle savedInstanceState) { 201 super.onCreate(savedInstanceState); 202 final View view = new View(this); 203 setContentView(view); 204 WindowManager.LayoutParams lp = getWindow().getAttributes(); 205 lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 206 getWindow().setAttributes(lp); 207 } 208 addOverlayWindow(int types, int sides, boolean ignoreVis)209 void addOverlayWindow(int types, int sides, boolean ignoreVis) { 210 final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(); 211 // In targets like automotive portrait UI, when the system bars are hidden, the 212 // system can change the bounds of the app to be fullscreen which can cause issues 213 // with the location. Use TYPE_APPLICATION_OVERLAY so that overlay window always ends 214 // up taking the fullscreen bounds. 215 attrs.type = TYPE_APPLICATION_OVERLAY; 216 attrs.width = MATCH_PARENT; 217 attrs.height = MATCH_PARENT; 218 attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 219 attrs.flags = FLAG_NOT_FOCUSABLE; 220 attrs.setFitInsetsTypes(types); 221 attrs.setFitInsetsSides(sides); 222 attrs.setFitInsetsIgnoringVisibility(ignoreVis); 223 mOverlayWindowRoot = new View(this); 224 getWindowManager().addView(mOverlayWindowRoot, attrs); 225 } 226 removeOverlayWindow()227 void removeOverlayWindow() { 228 getWindowManager().removeViewImmediate(mOverlayWindowRoot); 229 } 230 getOverlayWindowRoot()231 View getOverlayWindowRoot() { 232 return mOverlayWindowRoot; 233 } 234 assertMatchesWindowBounds()235 void assertMatchesWindowBounds() { 236 final View rootView = getWindow().getDecorView(); 237 final Rect windowMetricsBounds = 238 getWindowManager().getCurrentWindowMetrics().getBounds(); 239 assertEquals(windowMetricsBounds.width(), rootView.getWidth()); 240 assertEquals(windowMetricsBounds.height(), rootView.getHeight()); 241 final int[] locationOnScreen = new int[2]; 242 rootView.getLocationOnScreen(locationOnScreen); 243 assertEquals(locationOnScreen[0] /* expected x */, windowMetricsBounds.left); 244 assertEquals(locationOnScreen[1] /* expected y */, windowMetricsBounds.top); 245 } 246 } 247 } 248